mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
38 Commits
3bd9149d4b
...
083b9f98db
Author | SHA1 | Date | |
---|---|---|---|
|
083b9f98db | ||
|
d02960eb57 | ||
|
69b541d64d | ||
|
b17ae6135e | ||
|
2270359cd4 | ||
|
24e5751ece | ||
|
50f0a8343c | ||
|
d30908522a | ||
|
b9dce05ca1 | ||
|
9a8d6e7b41 | ||
|
4a10702f6c | ||
|
a113687d56 | ||
|
5fe818e17d | ||
|
6f3f567b58 | ||
|
429e0d8183 | ||
|
ab414558e8 | ||
|
e90f99bd4a | ||
|
99ffe09b63 | ||
|
9a511fecb5 | ||
|
6940435c03 | ||
|
f63a757b1e | ||
|
dd19552f88 | ||
|
c077ad70fd | ||
|
4b1ae72f59 | ||
|
355dd453bd | ||
|
cbecbe2bad | ||
|
6389db14a2 | ||
|
4bccf9d009 | ||
|
a4bfa6cb15 | ||
|
a36f6210c0 | ||
|
c346c1d75b | ||
|
df33bc3e27 | ||
|
60883e1a44 | ||
|
813e3b28af | ||
|
26feb26237 | ||
|
df821f0177 | ||
|
e058bb6fd3 | ||
|
15dfb02f9f |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -7,7 +7,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
go: ['1.19.x', '1.20.x', '1.21.x', '1.22.x', '1.23.x']
|
go: ['1.22.x', '1.23.x']
|
||||||
name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
|
name: Test with Go ${{ matrix.go }} on ${{ matrix.os }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
|
@ -188,14 +188,7 @@ func (c *Context) addPlayingPlayer(p *playerImpl) {
|
|||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
c.playingPlayers[p] = struct{}{}
|
c.playingPlayers[p] = struct{}{}
|
||||||
|
|
||||||
// (reflect.Type).Comparable() is enough here, as reflect.TypeOf should always return a dynamic (non-interface) type.
|
if !reflect.ValueOf(p.sourceIdent()).Comparable() {
|
||||||
// If reflect.TypeOf returned an interface type, this check would be meaningless.
|
|
||||||
// See these for more details:
|
|
||||||
// * https://pkg.go.dev/reflect#TypeOf
|
|
||||||
// * https://pkg.go.dev/reflect#Type.Comparable
|
|
||||||
//
|
|
||||||
// (*reflect.Value).Comparable() is more intuitive but this was introduced in Go 1.20.
|
|
||||||
if !reflect.TypeOf(p.sourceIdent()).Comparable() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ func newContext(sampleRate int) (context, chan struct{}, error) {
|
|||||||
ChannelCount: channelCount,
|
ChannelCount: channelCount,
|
||||||
Format: oto.FormatFloat32LE,
|
Format: oto.FormatFloat32LE,
|
||||||
})
|
})
|
||||||
err = addErrorInfoForContextCreation(err)
|
err = addErrorInfo(err)
|
||||||
return &contextProxy{ctx}, ready, err
|
return &contextProxy{ctx}, ready, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,9 +35,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addErrorInfoForContextCreation adds an additional information to the error when creating an audio context.
|
// addErrorInfo adds an additional information to the error when creating an audio context.
|
||||||
// See also hajimehoshi/oto#93.
|
// See also ebitengine/oto#93.
|
||||||
func addErrorInfoForContextCreation(err error) error {
|
func addErrorInfo(err error) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,6 @@
|
|||||||
|
|
||||||
package audio
|
package audio
|
||||||
|
|
||||||
func addErrorInfoForContextCreation(err error) error {
|
func addErrorInfo(err error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -93,10 +93,3 @@ func (r *float32BytesReader) Seek(offset int64, whence int) (int64, error) {
|
|||||||
}
|
}
|
||||||
return n / 2 * 4, nil
|
return n / 2 * 4, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,7 @@ package convert_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"math/rand" // TODO: Use math/rand/v2 when the minimum supported version becomes Go 1.22.
|
"math/rand/v2"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ import (
|
|||||||
func randInt16s(n int) []int16 {
|
func randInt16s(n int) []int16 {
|
||||||
r := make([]int16, n)
|
r := make([]int16, n)
|
||||||
for i := range r {
|
for i := range r {
|
||||||
r[i] = int16(rand.Intn(1<<16) - (1 << 15))
|
r[i] = int16(rand.IntN(1<<16) - (1 << 15))
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@ -69,15 +69,14 @@ func TestFloat32(t *testing.T) {
|
|||||||
name = "seek"
|
name = "seek"
|
||||||
}
|
}
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
// Note that unsafe.SliceData is available as of Go 1.20.
|
|
||||||
var in, out []byte
|
var in, out []byte
|
||||||
if len(c.In) > 0 {
|
if len(c.In) > 0 {
|
||||||
outF32 := make([]float32, len(c.In))
|
outF32 := make([]float32, len(c.In))
|
||||||
for i := range c.In {
|
for i := range c.In {
|
||||||
outF32[i] = float32(c.In[i]) / (1 << 15)
|
outF32[i] = float32(c.In[i]) / (1 << 15)
|
||||||
}
|
}
|
||||||
in = unsafe.Slice((*byte)(unsafe.Pointer(&c.In[0])), len(c.In)*2)
|
in = unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(c.In))), len(c.In)*2)
|
||||||
out = unsafe.Slice((*byte)(unsafe.Pointer(&outF32[0])), len(outF32)*4)
|
out = unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(outF32))), len(outF32)*4)
|
||||||
}
|
}
|
||||||
r := convert.NewFloat32BytesReaderFromInt16BytesReader(bytes.NewReader(in)).(io.ReadSeeker)
|
r := convert.NewFloat32BytesReaderFromInt16BytesReader(bytes.NewReader(in)).(io.ReadSeeker)
|
||||||
var got []byte
|
var got []byte
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/rand" // TODO: Use math/rand/v2 when the minimum supported version becomes Go 1.22.
|
"math/rand/v2"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
|
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
|
||||||
|
@ -17,8 +17,8 @@ package convert_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io" // TODO: Use math/rand/v2 when the minimum supported version becomes Go 1.22.
|
"io"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
|
"github.com/hajimehoshi/ebiten/v2/audio/internal/convert"
|
||||||
@ -101,7 +101,7 @@ func TestStereoI16(t *testing.T) {
|
|||||||
func randBytes(n int) []byte {
|
func randBytes(n int) []byte {
|
||||||
r := make([]byte, n)
|
r := make([]byte, n)
|
||||||
for i := range r {
|
for i := range r {
|
||||||
r[i] = byte(rand.Intn(256))
|
r[i] = byte(rand.IntN(256))
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ func (f *playerFactory) suspend() error {
|
|||||||
if f.context == nil {
|
if f.context == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return f.context.Suspend()
|
return addErrorInfo(f.context.Suspend())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *playerFactory) resume() error {
|
func (f *playerFactory) resume() error {
|
||||||
@ -122,7 +122,7 @@ func (f *playerFactory) resume() error {
|
|||||||
if f.context == nil {
|
if f.context == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return f.context.Resume()
|
return addErrorInfo(f.context.Resume())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *playerFactory) error() error {
|
func (f *playerFactory) error() error {
|
||||||
@ -132,7 +132,7 @@ func (f *playerFactory) error() error {
|
|||||||
if f.context == nil {
|
if f.context == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return f.context.Err()
|
return addErrorInfo(f.context.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *playerFactory) initContextIfNeeded() (<-chan struct{}, error) {
|
func (f *playerFactory) initContextIfNeeded() (<-chan struct{}, error) {
|
||||||
@ -263,7 +263,7 @@ func (p *playerImpl) Close() error {
|
|||||||
}()
|
}()
|
||||||
p.player.Pause()
|
p.player.Pause()
|
||||||
p.stopwatch.stop()
|
p.stopwatch.stop()
|
||||||
return p.player.Close()
|
return addErrorInfo(p.player.Close())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -293,7 +293,7 @@ func (p *playerImpl) SetPosition(offset time.Duration) error {
|
|||||||
|
|
||||||
pos := p.stream.timeDurationToPos(offset)
|
pos := p.stream.timeDurationToPos(offset)
|
||||||
if _, err := p.player.Seek(pos, io.SeekStart); err != nil {
|
if _, err := p.player.Seek(pos, io.SeekStart); err != nil {
|
||||||
return err
|
return addErrorInfo(err)
|
||||||
}
|
}
|
||||||
p.lastSamples = -1
|
p.lastSamples = -1
|
||||||
// Just after setting a position, the buffer size should be 0 as no data is sent.
|
// Just after setting a position, the buffer size should be 0 as no data is sent.
|
||||||
@ -312,7 +312,7 @@ func (p *playerImpl) Err() error {
|
|||||||
if p.player == nil {
|
if p.player == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return p.player.Err()
|
return addErrorInfo(p.player.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playerImpl) SetBufferSize(bufferSize time.Duration) {
|
func (p *playerImpl) SetBufferSize(bufferSize time.Duration) {
|
||||||
|
@ -34,13 +34,6 @@ type int16BytesReader struct {
|
|||||||
fbuf []float32
|
fbuf []float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *int16BytesReader) Read(buf []byte) (int, error) {
|
func (r *int16BytesReader) Read(buf []byte) (int, error) {
|
||||||
if r.eof {
|
if r.eof {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
|
@ -29,13 +29,12 @@ import {{.JavaPkg}}.ebitenmobileview.Renderer;
|
|||||||
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;
|
import {{.JavaPkg}}.{{.PrefixLower}}.EbitenView;
|
||||||
|
|
||||||
class EbitenSurfaceView extends GLSurfaceView implements Renderer {
|
class EbitenSurfaceView extends GLSurfaceView implements Renderer {
|
||||||
|
// As GLSurfaceView can be recreated, the states must be static (#3097).
|
||||||
|
static private boolean errored_ = false;
|
||||||
|
static private boolean onceSurfaceCreated_ = false;
|
||||||
|
static private boolean contextLost_ = false;
|
||||||
|
|
||||||
private class EbitenRenderer implements GLSurfaceView.Renderer {
|
private class EbitenRenderer implements GLSurfaceView.Renderer {
|
||||||
|
|
||||||
private boolean errored_ = false;
|
|
||||||
private boolean onceSurfaceCreated_ = false;
|
|
||||||
private boolean contextLost_ = false;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrawFrame(GL10 gl) {
|
public void onDrawFrame(GL10 gl) {
|
||||||
if (errored_) {
|
if (errored_) {
|
||||||
@ -107,7 +106,7 @@ class EbitenSurfaceView extends GLSurfaceView implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onContextLost() {
|
private void onContextLost() {
|
||||||
Log.v("Go", "Kill the application due to a context lost");
|
Log.e("Go", "The application was killed due to context loss");
|
||||||
// TODO: Relaunch this application for better UX (#805).
|
// TODO: Relaunch this application for better UX (#805).
|
||||||
Runtime.getRuntime().exit(0);
|
Runtime.getRuntime().exit(0);
|
||||||
}
|
}
|
||||||
|
@ -161,6 +161,12 @@ import (
|
|||||||
return tmp, err
|
return tmp, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// runtime.Version() is the current executing Go version. For example, this is the version of the toolchain directive in go.mod.
|
||||||
|
// This might differ from the Go command version under the temporary directory.
|
||||||
|
// To avoid the version mismatch, set the toolchain explicitly (#3086).
|
||||||
|
if err := runGo("mod", "edit", "-toolchain="+runtime.Version()); err != nil {
|
||||||
|
return tmp, err
|
||||||
|
}
|
||||||
if err := runGo("mod", "tidy"); err != nil {
|
if err := runGo("mod", "tidy"); err != nil {
|
||||||
return tmp, err
|
return tmp, err
|
||||||
}
|
}
|
||||||
|
28
cursor.go
28
cursor.go
@ -19,17 +19,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CursorModeType represents a render and coordinate mode of a mouse cursor.
|
// CursorModeType represents a render and coordinate mode of a mouse cursor.
|
||||||
type CursorModeType = ui.CursorMode
|
type CursorModeType int
|
||||||
|
|
||||||
// CursorModeTypes
|
// CursorModeTypes
|
||||||
const (
|
const (
|
||||||
CursorModeVisible CursorModeType = ui.CursorModeVisible
|
CursorModeVisible CursorModeType = CursorModeType(ui.CursorModeVisible)
|
||||||
CursorModeHidden CursorModeType = ui.CursorModeHidden
|
CursorModeHidden CursorModeType = CursorModeType(ui.CursorModeHidden)
|
||||||
CursorModeCaptured CursorModeType = ui.CursorModeCaptured
|
CursorModeCaptured CursorModeType = CursorModeType(ui.CursorModeCaptured)
|
||||||
)
|
)
|
||||||
|
|
||||||
// CursorShapeType represents a shape of a mouse cursor.
|
// CursorShapeType represents a shape of a mouse cursor.
|
||||||
type CursorShapeType = ui.CursorShape
|
type CursorShapeType int
|
||||||
|
|
||||||
// CursorShapeTypes
|
// CursorShapeTypes
|
||||||
const (
|
const (
|
||||||
@ -44,3 +44,21 @@ const (
|
|||||||
CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove)
|
CursorShapeMove CursorShapeType = CursorShapeType(ui.CursorShapeMove)
|
||||||
CursorShapeNotAllowed CursorShapeType = CursorShapeType(ui.CursorShapeNotAllowed)
|
CursorShapeNotAllowed CursorShapeType = CursorShapeType(ui.CursorShapeNotAllowed)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CursorShape returns the current cursor shape.
|
||||||
|
//
|
||||||
|
// CursorShape returns CursorShapeDefault on mobiles.
|
||||||
|
//
|
||||||
|
// CursorShape is concurrent-safe.
|
||||||
|
func CursorShape() CursorShapeType {
|
||||||
|
return CursorShapeType(ui.Get().CursorShape())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCursorShape sets the cursor shape.
|
||||||
|
//
|
||||||
|
// If the platform doesn't implement the given shape, the default cursor shape is used.
|
||||||
|
//
|
||||||
|
// SetCursorShape is concurrent-safe.
|
||||||
|
func SetCursorShape(shape CursorShapeType) {
|
||||||
|
ui.Get().SetCursorShape(ui.CursorShape(shape))
|
||||||
|
}
|
||||||
|
@ -15,16 +15,9 @@
|
|||||||
package twenty48
|
package twenty48
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ScreenWidth = 420
|
ScreenWidth = 420
|
||||||
ScreenHeight = 600
|
ScreenHeight = 600
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -261,9 +261,9 @@ func addRandomTile(tiles map[*Tile]struct{}, size int) error {
|
|||||||
if len(availableCells) == 0 {
|
if len(availableCells) == 0 {
|
||||||
return errors.New("twenty48: there is no space to add a new tile")
|
return errors.New("twenty48: there is no space to add a new tile")
|
||||||
}
|
}
|
||||||
c := availableCells[rand.Intn(len(availableCells))]
|
c := availableCells[rand.IntN(len(availableCells))]
|
||||||
v := 2
|
v := 2
|
||||||
if rand.Intn(10) == 0 {
|
if rand.IntN(10) == 0 {
|
||||||
v = 4
|
v = 4
|
||||||
}
|
}
|
||||||
x := c % size
|
x := c % size
|
||||||
|
@ -163,19 +163,9 @@ func loadImage(data []byte) (*ebiten.Image, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// max returns the largest of x or y.
|
// max returns the largest of x or y.
|
||||||
func max(x, y int) int {
|
|
||||||
if x > y {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxSide returns the largest side of a or b images.
|
// maxSide returns the largest side of a or b images.
|
||||||
func maxSide(a, b *ebiten.Image) int {
|
func maxSide(a, b *ebiten.Image) int {
|
||||||
return max(
|
return max(a.Bounds().Dx(), b.Bounds().Dx(), a.Bounds().Dy(), b.Bounds().Dy())
|
||||||
max(a.Bounds().Dx(), b.Bounds().Dx()),
|
|
||||||
max(a.Bounds().Dy(), b.Bounds().Dy()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawCenteredText is a util function for drawing blend mode description.
|
// drawCenteredText is a util function for drawing blend mode description.
|
||||||
|
@ -187,13 +187,6 @@ func (f *Field) Update() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b float64) float64 {
|
|
||||||
if a > b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func flushingColor(rate float64) colorm.ColorM {
|
func flushingColor(rate float64) colorm.ColorM {
|
||||||
var clr colorm.ColorM
|
var clr colorm.ColorM
|
||||||
alpha := min(1, rate*2)
|
alpha := min(1, rate*2)
|
||||||
|
@ -20,9 +20,8 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/colorm"
|
"github.com/hajimehoshi/ebiten/v2/colorm"
|
||||||
@ -139,10 +138,6 @@ type GameScene struct {
|
|||||||
gameover bool
|
gameover bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGameScene() *GameScene {
|
func NewGameScene() *GameScene {
|
||||||
return &GameScene{
|
return &GameScene{
|
||||||
field: &Field{},
|
field: &Field{},
|
||||||
@ -194,7 +189,7 @@ const (
|
|||||||
|
|
||||||
func (s *GameScene) choosePiece() *Piece {
|
func (s *GameScene) choosePiece() *Piece {
|
||||||
num := int(BlockTypeMax)
|
num := int(BlockTypeMax)
|
||||||
blockType := BlockType(rand.Intn(num) + 1)
|
blockType := BlockType(rand.IntN(num) + 1)
|
||||||
return Pieces[blockType]
|
return Pieces[blockType]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
|
@ -17,8 +17,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
@ -102,7 +101,7 @@ func (g *Game) updateFireIntensityPerPixel(currentPixelIndex int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d := rand.Intn(3)
|
d := rand.IntN(3)
|
||||||
newI := int(g.indices[below]) - d
|
newI := int(g.indices[below]) - d
|
||||||
if newI < 0 {
|
if newI < 0 {
|
||||||
newI = 0
|
newI = 0
|
||||||
@ -139,8 +138,6 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
ebiten.SetWindowSize(screenWidth*6, screenHeight*6)
|
ebiten.SetWindowSize(screenWidth*6, screenHeight*6)
|
||||||
ebiten.SetWindowTitle("Doom Fire (Ebitengine Demo)")
|
ebiten.SetWindowTitle("Doom Fire (Ebitengine Demo)")
|
||||||
if err := ebiten.RunGame(NewGame()); err != nil {
|
if err := ebiten.RunGame(NewGame()); err != nil {
|
||||||
|
@ -20,8 +20,7 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -29,10 +28,6 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
screenWidth = 640
|
screenWidth = 640
|
||||||
screenHeight = 480
|
screenHeight = 480
|
||||||
@ -196,8 +191,8 @@ func NewGame() *Game {
|
|||||||
s := &Sprite{
|
s := &Sprite{
|
||||||
image: ebitenImage,
|
image: ebitenImage,
|
||||||
alphaImage: ebitenAlphaImage,
|
alphaImage: ebitenAlphaImage,
|
||||||
x: rand.Intn(screenWidth - w),
|
x: rand.IntN(screenWidth - w),
|
||||||
y: rand.Intn(screenHeight - h),
|
y: rand.IntN(screenHeight - h),
|
||||||
}
|
}
|
||||||
sprites = append(sprites, s)
|
sprites = append(sprites, s)
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,7 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/audio"
|
"github.com/hajimehoshi/ebiten/v2/audio"
|
||||||
@ -44,10 +43,6 @@ var flagCRT = flag.Bool("crt", false, "enable the CRT effect")
|
|||||||
//go:embed crt.go
|
//go:embed crt.go
|
||||||
var crtGo []byte
|
var crtGo []byte
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
func floorDiv(x, y int) int {
|
func floorDiv(x, y int) int {
|
||||||
d := x / y
|
d := x / y
|
||||||
if d*y == x || x >= 0 {
|
if d*y == x || x >= 0 {
|
||||||
@ -150,7 +145,7 @@ func (g *Game) init() {
|
|||||||
g.cameraY = 0
|
g.cameraY = 0
|
||||||
g.pipeTileYs = make([]int, 256)
|
g.pipeTileYs = make([]int, 256)
|
||||||
for i := range g.pipeTileYs {
|
for i := range g.pipeTileYs {
|
||||||
g.pipeTileYs[i] = rand.Intn(6) + 2
|
g.pipeTileYs[i] = rand.IntN(6) + 2
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.audioContext == nil {
|
if g.audioContext == nil {
|
||||||
|
@ -19,8 +19,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
"github.com/hajimehoshi/ebiten/v2/examples/resources/fonts"
|
||||||
@ -94,10 +93,6 @@ func init() {
|
|||||||
mplusFaceSource = s
|
mplusFaceSource = s
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
counter int
|
counter int
|
||||||
kanjiText string
|
kanjiText string
|
||||||
@ -110,14 +105,14 @@ func (g *Game) Update() error {
|
|||||||
g.kanjiText = ""
|
g.kanjiText = ""
|
||||||
for j := 0; j < 6; j++ {
|
for j := 0; j < 6; j++ {
|
||||||
for i := 0; i < 12; i++ {
|
for i := 0; i < 12; i++ {
|
||||||
g.kanjiText += string(jaKanjis[rand.Intn(len(jaKanjis))])
|
g.kanjiText += string(jaKanjis[rand.IntN(len(jaKanjis))])
|
||||||
}
|
}
|
||||||
g.kanjiText += "\n"
|
g.kanjiText += "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
g.kanjiTextColor.R = 0x80 + uint8(rand.Intn(0x7f))
|
g.kanjiTextColor.R = 0x80 + uint8(rand.IntN(0x7f))
|
||||||
g.kanjiTextColor.G = 0x80 + uint8(rand.Intn(0x7f))
|
g.kanjiTextColor.G = 0x80 + uint8(rand.IntN(0x7f))
|
||||||
g.kanjiTextColor.B = 0x80 + uint8(rand.Intn(0x7f))
|
g.kanjiTextColor.B = 0x80 + uint8(rand.IntN(0x7f))
|
||||||
g.kanjiTextColor.A = 0xff
|
g.kanjiTextColor.A = 0xff
|
||||||
}
|
}
|
||||||
g.counter++
|
g.counter++
|
||||||
|
@ -16,8 +16,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Level represents a Game level.
|
// Level represents a Game level.
|
||||||
@ -56,9 +55,6 @@ func NewLevel() (*Level, error) {
|
|||||||
return nil, fmt.Errorf("failed to load embedded spritesheet: %s", err)
|
return nil, fmt.Errorf("failed to load embedded spritesheet: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a unique permutation each time.
|
|
||||||
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
|
|
||||||
|
|
||||||
// Fill each tile with one or more sprites randomly.
|
// Fill each tile with one or more sprites randomly.
|
||||||
l.tiles = make([][]*Tile, l.h)
|
l.tiles = make([][]*Tile, l.h)
|
||||||
for y := 0; y < l.h; y++ {
|
for y := 0; y < l.h; y++ {
|
||||||
@ -66,7 +62,7 @@ func NewLevel() (*Level, error) {
|
|||||||
for x := 0; x < l.w; x++ {
|
for x := 0; x < l.w; x++ {
|
||||||
t := &Tile{}
|
t := &Tile{}
|
||||||
isBorderSpace := x == 0 || y == 0 || x == l.w-1 || y == l.h-1
|
isBorderSpace := x == 0 || y == 0 || x == l.w-1 || y == l.h-1
|
||||||
val := r.Intn(1000)
|
val := rand.IntN(1000)
|
||||||
switch {
|
switch {
|
||||||
case isBorderSpace || val < 275:
|
case isBorderSpace || val < 275:
|
||||||
t.AddSprite(ss.Wall)
|
t.AddSprite(ss.Wall)
|
||||||
|
@ -8,16 +8,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
// World represents the game state.
|
// World represents the game state.
|
||||||
type World struct {
|
type World struct {
|
||||||
area []bool
|
area []bool
|
||||||
@ -39,8 +34,8 @@ func NewWorld(width, height int, maxInitLiveCells int) *World {
|
|||||||
// init inits world with a random state.
|
// init inits world with a random state.
|
||||||
func (w *World) init(maxLiveCells int) {
|
func (w *World) init(maxLiveCells int) {
|
||||||
for i := 0; i < maxLiveCells; i++ {
|
for i := 0; i < maxLiveCells; i++ {
|
||||||
x := rand.Intn(w.width)
|
x := rand.IntN(w.width)
|
||||||
y := rand.Intn(w.height)
|
y := rand.IntN(w.height)
|
||||||
w.area[y*w.width+x] = true
|
w.area[y*w.width+x] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,20 +91,6 @@ func (w *World) Draw(pix []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// neighbourCount calculates the Moore neighborhood of (x, y).
|
// neighbourCount calculates the Moore neighborhood of (x, y).
|
||||||
func neighbourCount(a []bool, width, height, x, y int) int {
|
func neighbourCount(a []bool, width, height, x, y int) int {
|
||||||
c := 0
|
c := 0
|
||||||
|
@ -27,13 +27,6 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||||
)
|
)
|
||||||
|
|
||||||
func min(x, y int) int {
|
|
||||||
if x < y {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
whiteImage = ebiten.NewImage(3, 3)
|
whiteImage = ebiten.NewImage(3, 3)
|
||||||
|
|
||||||
|
@ -21,8 +21,7 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
rmascot "github.com/hajimehoshi/ebiten/v2/examples/resources/images/mascot"
|
rmascot "github.com/hajimehoshi/ebiten/v2/examples/resources/images/mascot"
|
||||||
@ -60,10 +59,6 @@ func init() {
|
|||||||
gopher3 = ebiten.NewImageFromImage(img3)
|
gopher3 = ebiten.NewImageFromImage(img3)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
type mascot struct {
|
type mascot struct {
|
||||||
x16 int
|
x16 int
|
||||||
y16 int
|
y16 int
|
||||||
@ -105,8 +100,8 @@ func (m *mascot) Update() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the mascto is on the ground, cause an action in random.
|
// If the mascto is on the ground, cause an action in random.
|
||||||
if rand.Intn(60) == 0 && m.y16 == 0 {
|
if rand.IntN(60) == 0 && m.y16 == 0 {
|
||||||
switch rand.Intn(2) {
|
switch rand.IntN(2) {
|
||||||
case 0:
|
case 0:
|
||||||
// Jump.
|
// Jump.
|
||||||
m.vy16 = -240
|
m.vy16 = -240
|
||||||
|
@ -145,20 +145,6 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||||||
return screenWidth, screenHeight
|
return screenWidth, screenHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
|
ebiten.SetWindowSize(screenWidth*2, screenHeight*2)
|
||||||
ebiten.SetWindowTitle("Masking (Ebitengine Demo)")
|
ebiten.SetWindowTitle("Masking (Ebitengine Demo)")
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
screenWidth = 800
|
screenWidth = 1000
|
||||||
screenHeight = 480
|
screenHeight = 480
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,20 +44,28 @@ type Game struct {
|
|||||||
rotate bool
|
rotate bool
|
||||||
clip bool
|
clip bool
|
||||||
counter int
|
counter int
|
||||||
|
pause bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Update() error {
|
func (g *Game) Update() error {
|
||||||
g.counter++
|
|
||||||
if g.counter == 480 {
|
|
||||||
g.counter = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyR) {
|
||||||
g.rotate = !g.rotate
|
g.rotate = !g.rotate
|
||||||
}
|
}
|
||||||
if inpututil.IsKeyJustPressed(ebiten.KeyC) {
|
if inpututil.IsKeyJustPressed(ebiten.KeyC) {
|
||||||
g.clip = !g.clip
|
g.clip = !g.clip
|
||||||
}
|
}
|
||||||
|
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||||
|
g.pause = !g.pause
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.pause {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
g.counter++
|
||||||
|
if g.counter == 480 {
|
||||||
|
g.counter = 0
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +73,8 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||||||
s := 1.5 / math.Pow(1.01, float64(g.counter))
|
s := 1.5 / math.Pow(1.01, float64(g.counter))
|
||||||
|
|
||||||
clippedGophersImage := gophersImage.SubImage(image.Rect(100, 100, 200, 200)).(*ebiten.Image)
|
clippedGophersImage := gophersImage.SubImage(image.Rect(100, 100, 200, 200)).(*ebiten.Image)
|
||||||
for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
|
for i := range 3 {
|
||||||
|
//for i, f := range []ebiten.Filter{ebiten.FilterNearest, ebiten.FilterLinear} {
|
||||||
w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
|
w, h := gophersImage.Bounds().Dx(), gophersImage.Bounds().Dy()
|
||||||
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
@ -75,8 +84,15 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||||||
op.GeoM.Translate(float64(w)/2, float64(h)/2)
|
op.GeoM.Translate(float64(w)/2, float64(h)/2)
|
||||||
}
|
}
|
||||||
op.GeoM.Scale(s, s)
|
op.GeoM.Scale(s, s)
|
||||||
op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 64)
|
op.GeoM.Translate(32+float64(i*w)*s+float64(i*4), 100)
|
||||||
op.Filter = f
|
if i == 0 {
|
||||||
|
op.Filter = ebiten.FilterNearest
|
||||||
|
} else {
|
||||||
|
op.Filter = ebiten.FilterLinear
|
||||||
|
}
|
||||||
|
if i == 2 {
|
||||||
|
op.DisableMipmaps = true
|
||||||
|
}
|
||||||
if g.clip {
|
if g.clip {
|
||||||
screen.DrawImage(clippedGophersImage, op)
|
screen.DrawImage(clippedGophersImage, op)
|
||||||
} else {
|
} else {
|
||||||
@ -84,9 +100,10 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := fmt.Sprintf(`Minifying images (Nearest filter vs Linear filter):
|
msg := fmt.Sprintf(`Minifying images (Nearest filter, Linear filter (w/ mipmaps), and Linear Filter (w/o mipmaps)):
|
||||||
Press R to rotate the images.
|
Press R to rotate the images.
|
||||||
Press C to clip the images.
|
Press C to clip the images.
|
||||||
|
Click to pause and resume.
|
||||||
Scale: %0.2f`, s)
|
Scale: %0.2f`, s)
|
||||||
ebitenutil.DebugPrint(screen, msg)
|
ebitenutil.DebugPrint(screen, msg)
|
||||||
}
|
}
|
||||||
|
@ -23,18 +23,13 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
screenWidth = 640
|
screenWidth = 640
|
||||||
screenHeight = 480
|
screenHeight = 480
|
||||||
@ -110,7 +105,7 @@ func (s *sprite) draw(screen *ebiten.Image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newSprite(img *ebiten.Image) *sprite {
|
func newSprite(img *ebiten.Image) *sprite {
|
||||||
c := rand.Intn(50) + 300
|
c := rand.IntN(50) + 300
|
||||||
dir := rand.Float64() * 2 * math.Pi
|
dir := rand.Float64() * 2 * math.Pi
|
||||||
a := rand.Float64() * 2 * math.Pi
|
a := rand.Float64() * 2 * math.Pi
|
||||||
s := rand.Float64()*0.1 + 0.4
|
s := rand.Float64()*0.1 + 0.4
|
||||||
@ -136,7 +131,7 @@ func (g *Game) Update() error {
|
|||||||
g.sprites = list.New()
|
g.sprites = list.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.sprites.Len() < 500 && rand.Intn(4) < 3 {
|
if g.sprites.Len() < 500 && rand.IntN(4) < 3 {
|
||||||
// Emit
|
// Emit
|
||||||
g.sprites.PushBack(newSprite(smokeImage))
|
g.sprites.PushBack(newSprite(smokeImage))
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -30,10 +29,6 @@ const (
|
|||||||
screenHeight = 240
|
screenHeight = 240
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
offscreen *ebiten.Image
|
offscreen *ebiten.Image
|
||||||
}
|
}
|
||||||
@ -46,12 +41,12 @@ func NewGame() *Game {
|
|||||||
|
|
||||||
func (g *Game) Update() error {
|
func (g *Game) Update() error {
|
||||||
s := g.offscreen.Bounds().Size()
|
s := g.offscreen.Bounds().Size()
|
||||||
x := rand.Intn(s.X)
|
x := rand.IntN(s.X)
|
||||||
y := rand.Intn(s.Y)
|
y := rand.IntN(s.Y)
|
||||||
c := color.RGBA{
|
c := color.RGBA{
|
||||||
byte(rand.Intn(256)),
|
byte(rand.IntN(256)),
|
||||||
byte(rand.Intn(256)),
|
byte(rand.IntN(256)),
|
||||||
byte(rand.Intn(256)),
|
byte(rand.IntN(256)),
|
||||||
byte(0xff),
|
byte(0xff),
|
||||||
}
|
}
|
||||||
g.offscreen.Set(x, y, c)
|
g.offscreen.Set(x, y, c)
|
||||||
|
@ -18,8 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -59,10 +58,6 @@ type Game struct {
|
|||||||
level int
|
level int
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Game) collidesWithApple() bool {
|
func (g *Game) collidesWithApple() bool {
|
||||||
return g.snakeBody[0].X == g.apple.X &&
|
return g.snakeBody[0].X == g.apple.X &&
|
||||||
g.snakeBody[0].Y == g.apple.Y
|
g.snakeBody[0].Y == g.apple.Y
|
||||||
@ -130,8 +125,8 @@ func (g *Game) Update() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if g.collidesWithApple() {
|
if g.collidesWithApple() {
|
||||||
g.apple.X = rand.Intn(xGridCountInScreen - 1)
|
g.apple.X = rand.IntN(xGridCountInScreen - 1)
|
||||||
g.apple.Y = rand.Intn(yGridCountInScreen - 1)
|
g.apple.Y = rand.IntN(yGridCountInScreen - 1)
|
||||||
g.snakeBody = append(g.snakeBody, Position{
|
g.snakeBody = append(g.snakeBody, Position{
|
||||||
X: g.snakeBody[len(g.snakeBody)-1].X,
|
X: g.snakeBody[len(g.snakeBody)-1].X,
|
||||||
Y: g.snakeBody[len(g.snakeBody)-1].Y,
|
Y: g.snakeBody[len(g.snakeBody)-1].Y,
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -119,9 +119,9 @@ func (g *Game) init() {
|
|||||||
g.sprites.num = 500
|
g.sprites.num = 500
|
||||||
for i := range g.sprites.sprites {
|
for i := range g.sprites.sprites {
|
||||||
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
|
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
|
||||||
x, y := rand.Intn(screenWidth-w), rand.Intn(screenHeight-h)
|
x, y := rand.IntN(screenWidth-w), rand.IntN(screenHeight-h)
|
||||||
vx, vy := 2*rand.Intn(2)-1, 2*rand.Intn(2)-1
|
vx, vy := 2*rand.IntN(2)-1, 2*rand.IntN(2)-1
|
||||||
a := rand.Intn(maxAngle)
|
a := rand.IntN(maxAngle)
|
||||||
g.sprites.sprites[i] = &Sprite{
|
g.sprites.sprites[i] = &Sprite{
|
||||||
imageWidth: w,
|
imageWidth: w,
|
||||||
imageHeight: h,
|
imageHeight: h,
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
_ "image/png"
|
_ "image/png"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -117,9 +117,9 @@ func (g *Game) init() {
|
|||||||
g.sprites.num = 500
|
g.sprites.num = 500
|
||||||
for i := range g.sprites.sprites {
|
for i := range g.sprites.sprites {
|
||||||
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
|
w, h := ebitenImage.Bounds().Dx(), ebitenImage.Bounds().Dy()
|
||||||
x, y := rand.Intn(screenWidth-w), rand.Intn(screenHeight-h)
|
x, y := rand.IntN(screenWidth-w), rand.IntN(screenHeight-h)
|
||||||
vx, vy := 2*rand.Intn(2)-1, 2*rand.Intn(2)-1
|
vx, vy := 2*rand.IntN(2)-1, 2*rand.IntN(2)-1
|
||||||
a := rand.Intn(maxAngle)
|
a := rand.IntN(maxAngle)
|
||||||
g.sprites.sprites[i] = &Sprite{
|
g.sprites.sprites[i] = &Sprite{
|
||||||
imageWidth: w,
|
imageWidth: w,
|
||||||
imageHeight: h,
|
imageHeight: h,
|
||||||
|
@ -20,8 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -127,8 +126,8 @@ type squiral struct {
|
|||||||
func (s *squiral) spawn(game *Game) {
|
func (s *squiral) spawn(game *Game) {
|
||||||
s.dead = false
|
s.dead = false
|
||||||
|
|
||||||
rx := rand.Intn(width-4) + 2
|
rx := rand.IntN(width-4) + 2
|
||||||
ry := rand.Intn(height-4) + 2
|
ry := rand.IntN(height-4) + 2
|
||||||
|
|
||||||
for dx := -2; dx <= 2; dx++ {
|
for dx := -2; dx <= 2; dx++ {
|
||||||
for dy := -2; dy <= 2; dy++ {
|
for dy := -2; dy <= 2; dy++ {
|
||||||
@ -140,15 +139,15 @@ func (s *squiral) spawn(game *Game) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.speed = rand.Intn(5) + 1
|
s.speed = rand.IntN(5) + 1
|
||||||
s.pos.x = rx
|
s.pos.x = rx
|
||||||
s.pos.y = ry
|
s.pos.y = ry
|
||||||
s.dir = rand.Intn(4)
|
s.dir = rand.IntN(4)
|
||||||
|
|
||||||
game.colorCycle = (game.colorCycle + 1) % len(palettes[game.selectedPalette].colors)
|
game.colorCycle = (game.colorCycle + 1) % len(palettes[game.selectedPalette].colors)
|
||||||
s.col = palettes[game.selectedPalette].colors[game.colorCycle]
|
s.col = palettes[game.selectedPalette].colors[game.colorCycle]
|
||||||
|
|
||||||
s.rot = rand.Intn(2)
|
s.rot = rand.IntN(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *squiral) step(game *Game) {
|
func (s *squiral) step(game *Game) {
|
||||||
@ -157,7 +156,7 @@ func (s *squiral) step(game *Game) {
|
|||||||
}
|
}
|
||||||
x, y := s.pos.x, s.pos.y // shorthands
|
x, y := s.pos.x, s.pos.y // shorthands
|
||||||
|
|
||||||
change := rand.Intn(1000)
|
change := rand.IntN(1000)
|
||||||
if change < 2 {
|
if change < 2 {
|
||||||
// On 0.2% of iterations, switch rotation direction.
|
// On 0.2% of iterations, switch rotation direction.
|
||||||
s.rot = (s.rot + 1) % 2
|
s.rot = (s.rot + 1) % 2
|
||||||
@ -267,10 +266,6 @@ func (a *automaton) step(game *Game) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
selectedPalette int
|
selectedPalette int
|
||||||
colorCycle int
|
colorCycle int
|
||||||
|
@ -17,8 +17,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||||
@ -97,7 +96,6 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||||
ebiten.SetWindowTitle("Stars (Ebitengine Demo)")
|
ebiten.SetWindowTitle("Stars (Ebitengine Demo)")
|
||||||
if err := ebiten.RunGame(NewGame()); err != nil {
|
if err := ebiten.RunGame(NewGame()); err != nil {
|
||||||
|
@ -22,12 +22,11 @@ import (
|
|||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
@ -55,7 +54,6 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -71,9 +69,9 @@ var (
|
|||||||
func createRandomIconImage() image.Image {
|
func createRandomIconImage() image.Image {
|
||||||
const size = 32
|
const size = 32
|
||||||
|
|
||||||
rf := float64(rand.Intn(0x100))
|
rf := float64(rand.IntN(0x100))
|
||||||
gf := float64(rand.Intn(0x100))
|
gf := float64(rand.IntN(0x100))
|
||||||
bf := float64(rand.Intn(0x100))
|
bf := float64(rand.IntN(0x100))
|
||||||
img := ebiten.NewImage(size, size)
|
img := ebiten.NewImage(size, size)
|
||||||
pix := make([]byte, 4*size*size)
|
pix := make([]byte, 4*size*size)
|
||||||
for j := 0; j < size; j++ {
|
for j := 0; j < size; j++ {
|
||||||
|
2
geom.go
2
geom.go
@ -121,7 +121,7 @@ func (g *GeoM) Translate(tx, ty float64) {
|
|||||||
g.ty += ty
|
g.ty += ty
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate rotates the matrix by theta.
|
// Rotate rotates the matrix clockwise by theta.
|
||||||
// The unit is radian.
|
// The unit is radian.
|
||||||
func (g *GeoM) Rotate(theta float64) {
|
func (g *GeoM) Rotate(theta float64) {
|
||||||
if theta == 0 {
|
if theta == 0 {
|
||||||
|
14
go.mod
14
go.mod
@ -1,29 +1,29 @@
|
|||||||
module github.com/hajimehoshi/ebiten/v2
|
module github.com/hajimehoshi/ebiten/v2
|
||||||
|
|
||||||
go 1.19
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ebitengine/gomobile v0.0.0-20240825043811-96c531f5bd83
|
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325
|
||||||
github.com/ebitengine/hideconsole v1.0.0
|
github.com/ebitengine/hideconsole v1.0.0
|
||||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.4
|
github.com/ebitengine/oto/v3 v3.3.0-alpha.4
|
||||||
github.com/ebitengine/purego v0.8.0-alpha.5
|
github.com/ebitengine/purego v0.8.0-alpha.5
|
||||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f
|
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f
|
||||||
github.com/go-text/typesetting v0.1.1
|
github.com/go-text/typesetting v0.1.1-0.20240522210117-2c045476f496
|
||||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.5
|
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.5
|
||||||
github.com/hajimehoshi/go-mp3 v0.3.4
|
github.com/hajimehoshi/go-mp3 v0.3.4
|
||||||
github.com/jakecoffman/cp v1.2.1
|
github.com/jakecoffman/cp v1.2.1
|
||||||
github.com/jezek/xgb v1.1.1
|
github.com/jezek/xgb v1.1.1
|
||||||
github.com/jfreymuth/oggvorbis v1.0.5
|
github.com/jfreymuth/oggvorbis v1.0.5
|
||||||
github.com/kisielk/errcheck v1.7.0
|
github.com/kisielk/errcheck v1.7.0
|
||||||
golang.org/x/image v0.19.0
|
golang.org/x/image v0.20.0
|
||||||
golang.org/x/sync v0.8.0
|
golang.org/x/sync v0.8.0
|
||||||
golang.org/x/sys v0.25.0
|
golang.org/x/sys v0.25.0
|
||||||
golang.org/x/text v0.17.0
|
golang.org/x/text v0.18.0
|
||||||
golang.org/x/tools v0.24.0
|
golang.org/x/tools v0.25.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/jfreymuth/vorbis v1.0.2 // indirect
|
github.com/jfreymuth/vorbis v1.0.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
golang.org/x/mod v0.20.0 // indirect
|
golang.org/x/mod v0.21.0 // indirect
|
||||||
)
|
)
|
||||||
|
25
go.sum
25
go.sum
@ -1,5 +1,5 @@
|
|||||||
github.com/ebitengine/gomobile v0.0.0-20240825043811-96c531f5bd83 h1:yA0CtFKYZI/db1snCOInRS0Z18QGZU6aBYkqUT0H6RI=
|
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325 h1:Gk1XUEttOk0/hb6Tq3WkmutWa0ZLhNn/6fc6XZpM7tM=
|
||||||
github.com/ebitengine/gomobile v0.0.0-20240825043811-96c531f5bd83/go.mod h1:n2NbB/F4d9wOXFzC7FT1ipERidmYWC5I4YNOYRs5N7I=
|
github.com/ebitengine/gomobile v0.0.0-20240911145611-4856209ac325/go.mod h1:ulhSQcbPioQrallSuIzF8l1NKQoD7xmMZc5NxzibUMY=
|
||||||
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE=
|
||||||
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
|
||||||
github.com/ebitengine/oto/v3 v3.3.0-alpha.4 h1:w9SD7kK4GgJULkh5pWVTToMA5Ia1bP7VxD4rIjQqb8M=
|
github.com/ebitengine/oto/v3 v3.3.0-alpha.4 h1:w9SD7kK4GgJULkh5pWVTToMA5Ia1bP7VxD4rIjQqb8M=
|
||||||
@ -8,9 +8,10 @@ github.com/ebitengine/purego v0.8.0-alpha.5 h1:M0+PSgsdVNczTB8ijX89HmYqCfb2HUuBE
|
|||||||
github.com/ebitengine/purego v0.8.0-alpha.5/go.mod h1:SQ56/omnSL8DdaBSKswoBvsMjgaWQyxyeMtb48sOskI=
|
github.com/ebitengine/purego v0.8.0-alpha.5/go.mod h1:SQ56/omnSL8DdaBSKswoBvsMjgaWQyxyeMtb48sOskI=
|
||||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk=
|
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f h1:ysqRe+lvUiL0dH5XzkH0Bz68bFMPJ4f5Si4L/HD9SGk=
|
||||||
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38=
|
github.com/gen2brain/mpeg v0.3.2-0.20240412154320-a2ac4fc8a46f/go.mod h1:i/ebyRRv/IoHixuZ9bElZnXbmfoUVPGQpdsJ4sVuX38=
|
||||||
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
|
github.com/go-text/typesetting v0.1.1-0.20240522210117-2c045476f496 h1:zgx3rOyOdRoA2GXWpfJkH7Zg248ookseRifdn9VSp5g=
|
||||||
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
github.com/go-text/typesetting v0.1.1-0.20240522210117-2c045476f496/go.mod h1:2+owI/sxa73XA581LAzVuEBZ3WEEV2pXeDswCH/3i1I=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
|
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20240317173224-1986cbe96c66/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.5 h1:gtIcN2INlD2qlfUiECuvbI0moNIoANgIY7MwgW4cFGE=
|
github.com/hajimehoshi/bitmapfont/v3 v3.2.0-alpha.5 h1:gtIcN2INlD2qlfUiECuvbI0moNIoANgIY7MwgW4cFGE=
|
||||||
@ -37,15 +38,16 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
|
||||||
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
||||||
|
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||||
|
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
@ -92,14 +94,15 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||||
|
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
85
image.go
85
image.go
@ -144,6 +144,16 @@ type DrawImageOptions struct {
|
|||||||
// Filter is a type of texture filter.
|
// Filter is a type of texture filter.
|
||||||
// The default (zero) value is FilterNearest.
|
// The default (zero) value is FilterNearest.
|
||||||
Filter Filter
|
Filter Filter
|
||||||
|
|
||||||
|
// DisableMipmaps disables mipmaps.
|
||||||
|
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
|
||||||
|
// Mipmap is useful to render a shrunk image with high quality.
|
||||||
|
// However, mipmaps can be expensive, especially on mobiles.
|
||||||
|
// When DisableMipmaps is true, mipmap is not used.
|
||||||
|
// When Filter is not FilterLinear, DisableMipmaps is ignored.
|
||||||
|
//
|
||||||
|
// The default (zero) value is false.
|
||||||
|
DisableMipmaps bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate.
|
// adjustPosition converts the position in the *ebiten.Image coordinate to the *ui.Image coordinate.
|
||||||
@ -273,7 +283,11 @@ func (i *Image) DrawImage(img *Image, options *DrawImageOptions) {
|
|||||||
hint = restorable.HintOverwriteDstRegion
|
hint = restorable.HintOverwriteDstRegion
|
||||||
}
|
}
|
||||||
|
|
||||||
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, canSkipMipmap(geoM, filter), false, hint)
|
skipMipmap := options.DisableMipmaps
|
||||||
|
if !skipMipmap {
|
||||||
|
skipMipmap = canSkipMipmap(geoM, filter)
|
||||||
|
}
|
||||||
|
i.image.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRuleFillAll, skipMipmap, false, hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
|
// overwritesDstRegion reports whether the given parameters overwrite the destination region completely.
|
||||||
@ -447,6 +461,16 @@ type DrawTrianglesOptions struct {
|
|||||||
//
|
//
|
||||||
// The default (zero) value is false.
|
// The default (zero) value is false.
|
||||||
AntiAlias bool
|
AntiAlias bool
|
||||||
|
|
||||||
|
// DisableMipmaps disables mipmaps.
|
||||||
|
// When Filter is FilterLinear and GeoM shrinks the image, mipmaps are used by default.
|
||||||
|
// Mipmap is useful to render a shrunk image with high quality.
|
||||||
|
// However, mipmaps can be expensive, especially on mobiles.
|
||||||
|
// When DisableMipmaps is true, mipmap is not used.
|
||||||
|
// When Filter is not FilterLinear, DisableMipmaps is ignored.
|
||||||
|
//
|
||||||
|
// The default (zero) value is false.
|
||||||
|
DisableMipmaps bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
|
// MaxIndicesCount is the maximum number of indices for DrawTriangles and DrawTrianglesShader.
|
||||||
@ -530,30 +554,32 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
|||||||
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
||||||
dst := i
|
dst := i
|
||||||
if options.ColorScaleMode == ColorScaleModeStraightAlpha {
|
if options.ColorScaleMode == ColorScaleModeStraightAlpha {
|
||||||
for i, v := range vertices {
|
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
|
||||||
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
|
for i := range vertices {
|
||||||
|
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||||
vs[i*graphics.VertexFloatCount] = dx
|
vs[i*graphics.VertexFloatCount] = dx
|
||||||
vs[i*graphics.VertexFloatCount+1] = dy
|
vs[i*graphics.VertexFloatCount+1] = dy
|
||||||
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
|
sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
|
||||||
vs[i*graphics.VertexFloatCount+2] = sx
|
vs[i*graphics.VertexFloatCount+2] = sx
|
||||||
vs[i*graphics.VertexFloatCount+3] = sy
|
vs[i*graphics.VertexFloatCount+3] = sy
|
||||||
vs[i*graphics.VertexFloatCount+4] = v.ColorR * v.ColorA * cr
|
vs[i*graphics.VertexFloatCount+4] = vertices[i].ColorR * vertices[i].ColorA * cr
|
||||||
vs[i*graphics.VertexFloatCount+5] = v.ColorG * v.ColorA * cg
|
vs[i*graphics.VertexFloatCount+5] = vertices[i].ColorG * vertices[i].ColorA * cg
|
||||||
vs[i*graphics.VertexFloatCount+6] = v.ColorB * v.ColorA * cb
|
vs[i*graphics.VertexFloatCount+6] = vertices[i].ColorB * vertices[i].ColorA * cb
|
||||||
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
|
vs[i*graphics.VertexFloatCount+7] = vertices[i].ColorA * ca
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i, v := range vertices {
|
// See comment above (#3103).
|
||||||
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
|
for i := range vertices {
|
||||||
|
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||||
vs[i*graphics.VertexFloatCount] = dx
|
vs[i*graphics.VertexFloatCount] = dx
|
||||||
vs[i*graphics.VertexFloatCount+1] = dy
|
vs[i*graphics.VertexFloatCount+1] = dy
|
||||||
sx, sy := img.adjustPositionF32(v.SrcX, v.SrcY)
|
sx, sy := img.adjustPositionF32(vertices[i].SrcX, vertices[i].SrcY)
|
||||||
vs[i*graphics.VertexFloatCount+2] = sx
|
vs[i*graphics.VertexFloatCount+2] = sx
|
||||||
vs[i*graphics.VertexFloatCount+3] = sy
|
vs[i*graphics.VertexFloatCount+3] = sy
|
||||||
vs[i*graphics.VertexFloatCount+4] = v.ColorR * cr
|
vs[i*graphics.VertexFloatCount+4] = vertices[i].ColorR * cr
|
||||||
vs[i*graphics.VertexFloatCount+5] = v.ColorG * cg
|
vs[i*graphics.VertexFloatCount+5] = vertices[i].ColorG * cg
|
||||||
vs[i*graphics.VertexFloatCount+6] = v.ColorB * cb
|
vs[i*graphics.VertexFloatCount+6] = vertices[i].ColorB * cb
|
||||||
vs[i*graphics.VertexFloatCount+7] = v.ColorA * ca
|
vs[i*graphics.VertexFloatCount+7] = vertices[i].ColorA * ca
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is := i.ensureTmpIndices(len(indices))
|
is := i.ensureTmpIndices(len(indices))
|
||||||
@ -576,7 +602,11 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), filter != builtinshader.FilterLinear, options.AntiAlias, restorable.HintNone)
|
skipMipmap := options.DisableMipmaps
|
||||||
|
if !skipMipmap {
|
||||||
|
skipMipmap = filter != builtinshader.FilterLinear
|
||||||
|
}
|
||||||
|
i.image.DrawTriangles(srcs, vs, is, blend, i.adjustedBounds(), [graphics.ShaderSrcImageCount]image.Rectangle{img.adjustedBounds()}, shader.shader, i.tmpUniforms, graphicsdriver.FillRule(options.FillRule), skipMipmap, options.AntiAlias, restorable.HintNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
// DrawTrianglesShaderOptions represents options for DrawTrianglesShader.
|
||||||
@ -690,24 +720,25 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
|
|||||||
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
vs := i.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
|
||||||
dst := i
|
dst := i
|
||||||
src := options.Images[0]
|
src := options.Images[0]
|
||||||
for i, v := range vertices {
|
// Avoid using `for i, v := range vertices` as adding `v` creates a copy from `vertices` unnecessarily on each loop (#3103).
|
||||||
dx, dy := dst.adjustPositionF32(v.DstX, v.DstY)
|
for i := range vertices {
|
||||||
|
dx, dy := dst.adjustPositionF32(vertices[i].DstX, vertices[i].DstY)
|
||||||
vs[i*graphics.VertexFloatCount] = dx
|
vs[i*graphics.VertexFloatCount] = dx
|
||||||
vs[i*graphics.VertexFloatCount+1] = dy
|
vs[i*graphics.VertexFloatCount+1] = dy
|
||||||
sx, sy := v.SrcX, v.SrcY
|
sx, sy := vertices[i].SrcX, vertices[i].SrcY
|
||||||
if src != nil {
|
if src != nil {
|
||||||
sx, sy = src.adjustPositionF32(sx, sy)
|
sx, sy = src.adjustPositionF32(sx, sy)
|
||||||
}
|
}
|
||||||
vs[i*graphics.VertexFloatCount+2] = sx
|
vs[i*graphics.VertexFloatCount+2] = sx
|
||||||
vs[i*graphics.VertexFloatCount+3] = sy
|
vs[i*graphics.VertexFloatCount+3] = sy
|
||||||
vs[i*graphics.VertexFloatCount+4] = v.ColorR
|
vs[i*graphics.VertexFloatCount+4] = vertices[i].ColorR
|
||||||
vs[i*graphics.VertexFloatCount+5] = v.ColorG
|
vs[i*graphics.VertexFloatCount+5] = vertices[i].ColorG
|
||||||
vs[i*graphics.VertexFloatCount+6] = v.ColorB
|
vs[i*graphics.VertexFloatCount+6] = vertices[i].ColorB
|
||||||
vs[i*graphics.VertexFloatCount+7] = v.ColorA
|
vs[i*graphics.VertexFloatCount+7] = vertices[i].ColorA
|
||||||
vs[i*graphics.VertexFloatCount+8] = v.Custom0
|
vs[i*graphics.VertexFloatCount+8] = vertices[i].Custom0
|
||||||
vs[i*graphics.VertexFloatCount+9] = v.Custom1
|
vs[i*graphics.VertexFloatCount+9] = vertices[i].Custom1
|
||||||
vs[i*graphics.VertexFloatCount+10] = v.Custom2
|
vs[i*graphics.VertexFloatCount+10] = vertices[i].Custom2
|
||||||
vs[i*graphics.VertexFloatCount+11] = v.Custom3
|
vs[i*graphics.VertexFloatCount+11] = vertices[i].Custom3
|
||||||
}
|
}
|
||||||
|
|
||||||
is := i.ensureTmpIndices(len(indices))
|
is := i.ensureTmpIndices(len(indices))
|
||||||
|
@ -22,10 +22,9 @@ import (
|
|||||||
"image/draw"
|
"image/draw"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
"github.com/hajimehoshi/ebiten/v2/examples/resources/images"
|
||||||
@ -37,10 +36,6 @@ import (
|
|||||||
// maxImageSize is a maximum image size that should work in almost every environment.
|
// maxImageSize is a maximum image size that should work in almost every environment.
|
||||||
const maxImageSize = 4096 - 2
|
const maxImageSize = 4096 - 2
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
}
|
|
||||||
|
|
||||||
func skipTooSlowTests(t *testing.T) bool {
|
func skipTooSlowTests(t *testing.T) bool {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("skipping test in short mode")
|
t.Skip("skipping test in short mode")
|
||||||
@ -348,26 +343,6 @@ func TestImageDeallocate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ordered interface {
|
|
||||||
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use the built-in function min from Go 1.21.
|
|
||||||
func min[T ordered](a, b T) T {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Use the built-in function max from Go 1.21.
|
|
||||||
func max[T ordered](a, b T) T {
|
|
||||||
if a < b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageBlendLighter(t *testing.T) {
|
func TestImageBlendLighter(t *testing.T) {
|
||||||
img0, _, err := openEbitenImage()
|
img0, _, err := openEbitenImage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -4292,7 +4267,7 @@ func TestImageAntiAlias(t *testing.T) {
|
|||||||
ebiten.BlendXor,
|
ebiten.BlendXor,
|
||||||
ebiten.BlendLighter,
|
ebiten.BlendLighter,
|
||||||
} {
|
} {
|
||||||
rnd := rand.New(rand.NewSource(0))
|
rnd := rand.New(rand.NewPCG(0, 0))
|
||||||
max := func(x, y, z byte) byte {
|
max := func(x, y, z byte) byte {
|
||||||
if x >= y && x >= z {
|
if x >= y && x >= z {
|
||||||
return x
|
return x
|
||||||
|
8
input.go
8
input.go
@ -79,7 +79,7 @@ func KeyName(key Key) string {
|
|||||||
//
|
//
|
||||||
// CursorPosition returns (0, 0) before the main loop on desktops and browsers.
|
// CursorPosition returns (0, 0) before the main loop on desktops and browsers.
|
||||||
//
|
//
|
||||||
// CursorPosition always returns (0, 0) on mobiles.
|
// CursorPosition always returns (0, 0) on mobile native applications.
|
||||||
//
|
//
|
||||||
// CursorPosition is concurrent-safe.
|
// CursorPosition is concurrent-safe.
|
||||||
func CursorPosition() (x, y int) {
|
func CursorPosition() (x, y int) {
|
||||||
@ -350,7 +350,7 @@ func UpdateStandardGamepadLayoutMappings(mappings string) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TouchID represents a touch's identifier.
|
// TouchID represents a touch's identifier.
|
||||||
type TouchID = ui.TouchID
|
type TouchID int
|
||||||
|
|
||||||
// AppendTouchIDs appends the current touch states to touches, and returns the extended buffer.
|
// AppendTouchIDs appends the current touch states to touches, and returns the extended buffer.
|
||||||
// Giving a slice that already has enough capacity works efficiently.
|
// Giving a slice that already has enough capacity works efficiently.
|
||||||
@ -446,7 +446,7 @@ func (i *inputState) appendTouchIDs(touches []TouchID) []TouchID {
|
|||||||
defer i.m.Unlock()
|
defer i.m.Unlock()
|
||||||
|
|
||||||
for _, t := range i.state.Touches {
|
for _, t := range i.state.Touches {
|
||||||
touches = append(touches, t.ID)
|
touches = append(touches, TouchID(t.ID))
|
||||||
}
|
}
|
||||||
return touches
|
return touches
|
||||||
}
|
}
|
||||||
@ -456,7 +456,7 @@ func (i *inputState) touchPosition(id TouchID) (int, int) {
|
|||||||
defer i.m.Unlock()
|
defer i.m.Unlock()
|
||||||
|
|
||||||
for _, t := range i.state.Touches {
|
for _, t := range i.state.Touches {
|
||||||
if id != t.ID {
|
if id != TouchID(t.ID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return t.X, t.Y
|
return t.X, t.Y
|
||||||
|
@ -16,7 +16,7 @@ package affine_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand/v2"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/affine"
|
"github.com/hajimehoshi/ebiten/v2/internal/affine"
|
||||||
|
@ -35,13 +35,6 @@ var (
|
|||||||
maxSize = 0
|
maxSize = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendDeferred(f func()) {
|
func appendDeferred(f func()) {
|
||||||
deferredM.Lock()
|
deferredM.Lock()
|
||||||
defer deferredM.Unlock()
|
defer deferredM.Unlock()
|
||||||
|
@ -314,8 +314,5 @@ func (i *Image) syncPixelsIfNeeded() {
|
|||||||
blend := graphicsdriver.BlendCopy
|
blend := graphicsdriver.BlendCopy
|
||||||
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
i.img.DrawTriangles(srcs, vs, is, blend, dr, [graphics.ShaderSrcImageCount]image.Rectangle{sr}, atlas.NearestFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintNone)
|
||||||
|
|
||||||
// TODO: Use clear if Go 1.21 is available.
|
clear(i.dotsBuffer)
|
||||||
for pos := range i.dotsBuffer {
|
|
||||||
delete(i.dotsBuffer, pos)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -64,13 +64,6 @@ func ActualTPS() float64 {
|
|||||||
return actualTPS
|
return actualTPS
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(a, b int64) int64 {
|
|
||||||
if a < b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func calcCountFromTPS(tps int64, now int64) int {
|
func calcCountFromTPS(tps int64, now int64) int {
|
||||||
if tps == 0 {
|
if tps == 0 {
|
||||||
return 0
|
return 0
|
||||||
|
@ -616,6 +616,5 @@ func bytePtrToString(p *byte) string {
|
|||||||
ptr = unsafe.Add(ptr, 1)
|
ptr = unsafe.Add(ptr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsafe.String(p, n) is available as of Go 1.20.
|
return unsafe.String(p, n)
|
||||||
return string(unsafe.Slice(p, n))
|
|
||||||
}
|
}
|
||||||
|
@ -507,13 +507,6 @@ func roundUpPower2(x int) int {
|
|||||||
return p2
|
return p2
|
||||||
}
|
}
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *uint32sBuffer) alloc(n int) []uint32 {
|
func (b *uint32sBuffer) alloc(n int) []uint32 {
|
||||||
buf := b.buf
|
buf := b.buf
|
||||||
if len(buf)+n > cap(buf) {
|
if len(buf)+n > cap(buf) {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package jsutil
|
package gl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
@ -27,77 +27,77 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
temporaryArrayBufferByteLength = 16
|
tmpArrayBufferByteLength = 16
|
||||||
|
|
||||||
// temporaryArrayBuffer is a temporary buffer used at gl.readPixels or gl.texSubImage2D.
|
// tmpArrayBuffer is a temporary buffer used at gl.readPixels or gl.texSubImage2D.
|
||||||
// The read data is converted to Go's byte slice as soon as possible.
|
// The read data is converted to Go's byte slice as soon as possible.
|
||||||
// To avoid often allocating ArrayBuffer, reuse the buffer whenever possible.
|
// To avoid often allocating ArrayBuffer, reuse the buffer whenever possible.
|
||||||
temporaryArrayBuffer = arrayBuffer.New(temporaryArrayBufferByteLength)
|
tmpArrayBuffer = arrayBuffer.New(tmpArrayBufferByteLength)
|
||||||
|
|
||||||
// temporaryUint8Array is a Uint8ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
// tmpUint8Array is a Uint8ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||||
temporaryUint8Array = uint8Array.New(temporaryArrayBuffer)
|
tmpUint8Array = uint8Array.New(tmpArrayBuffer)
|
||||||
|
|
||||||
// temporaryFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
// tmpFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||||
temporaryFloat32Array = float32Array.New(temporaryArrayBuffer)
|
tmpFloat32Array = float32Array.New(tmpArrayBuffer)
|
||||||
|
|
||||||
// temporaryInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
// tmpInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer.
|
||||||
temporaryInt32Array = int32Array.New(temporaryArrayBuffer)
|
tmpInt32Array = int32Array.New(tmpArrayBuffer)
|
||||||
)
|
)
|
||||||
|
|
||||||
func ensureTemporaryArrayBufferSize(byteLength int) {
|
func ensureTemporaryArrayBufferSize(byteLength int) {
|
||||||
if bufl := temporaryArrayBufferByteLength; bufl < byteLength {
|
if bufl := tmpArrayBufferByteLength; bufl < byteLength {
|
||||||
for bufl < byteLength {
|
for bufl < byteLength {
|
||||||
bufl *= 2
|
bufl *= 2
|
||||||
}
|
}
|
||||||
temporaryArrayBufferByteLength = bufl
|
tmpArrayBufferByteLength = bufl
|
||||||
temporaryArrayBuffer = arrayBuffer.New(bufl)
|
tmpArrayBuffer = arrayBuffer.New(bufl)
|
||||||
temporaryUint8Array = uint8Array.New(temporaryArrayBuffer)
|
tmpUint8Array = uint8Array.New(tmpArrayBuffer)
|
||||||
temporaryFloat32Array = float32Array.New(temporaryArrayBuffer)
|
tmpFloat32Array = float32Array.New(tmpArrayBuffer)
|
||||||
temporaryInt32Array = int32Array.New(temporaryArrayBuffer)
|
tmpInt32Array = int32Array.New(tmpArrayBuffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemporaryUint8ArrayFromUint8Slice returns a Uint8Array whose length is at least minLength from an uint8 slice.
|
// tmpUint8ArrayFromUint8Slice returns a Uint8Array whose length is at least minLength from an uint8 slice.
|
||||||
// Be careful that the length can exceed the given minLength.
|
// Be careful that the length can exceed the given minLength.
|
||||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||||
func TemporaryUint8ArrayFromUint8Slice(minLength int, data []uint8) js.Value {
|
func tmpUint8ArrayFromUint8Slice(minLength int, data []uint8) js.Value {
|
||||||
ensureTemporaryArrayBufferSize(minLength)
|
ensureTemporaryArrayBufferSize(minLength)
|
||||||
copyUint8SliceToTemporaryArrayBuffer(data)
|
copyUint8SliceToTemporaryArrayBuffer(data)
|
||||||
return temporaryUint8Array
|
return tmpUint8Array
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemporaryUint8ArrayFromUint16Slice returns a Uint8Array whose length is at least minLength from an uint16 slice.
|
// tmpUint8ArrayFromUint16Slice returns a Uint8Array whose length is at least minLength from an uint16 slice.
|
||||||
// Be careful that the length can exceed the given minLength.
|
// Be careful that the length can exceed the given minLength.
|
||||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||||
func TemporaryUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value {
|
func tmpUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value {
|
||||||
ensureTemporaryArrayBufferSize(minLength * 2)
|
ensureTemporaryArrayBufferSize(minLength * 2)
|
||||||
copySliceToTemporaryArrayBuffer(data)
|
copySliceToTemporaryArrayBuffer(data)
|
||||||
return temporaryUint8Array
|
return tmpUint8Array
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemporaryUint8ArrayFromFloat32Slice returns a Uint8Array whose length is at least minLength from a float32 slice.
|
// tmpUint8ArrayFromFloat32Slice returns a Uint8Array whose length is at least minLength from a float32 slice.
|
||||||
// Be careful that the length can exceed the given minLength.
|
// Be careful that the length can exceed the given minLength.
|
||||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||||
func TemporaryUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
|
func tmpUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
|
||||||
ensureTemporaryArrayBufferSize(minLength * 4)
|
ensureTemporaryArrayBufferSize(minLength * 4)
|
||||||
copySliceToTemporaryArrayBuffer(data)
|
copySliceToTemporaryArrayBuffer(data)
|
||||||
return temporaryUint8Array
|
return tmpUint8Array
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemporaryFloat32Array returns a Float32Array whose length is at least minLength.
|
// tmpFloat32ArrayFromFloat32Slice returns a Float32Array whose length is at least minLength.
|
||||||
// Be careful that the length can exceed the given minLength.
|
// Be careful that the length can exceed the given minLength.
|
||||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||||
func TemporaryFloat32Array(minLength int, data []float32) js.Value {
|
func tmpFloat32ArrayFromFloat32Slice(minLength int, data []float32) js.Value {
|
||||||
ensureTemporaryArrayBufferSize(minLength * 4)
|
ensureTemporaryArrayBufferSize(minLength * 4)
|
||||||
copySliceToTemporaryArrayBuffer(data)
|
copySliceToTemporaryArrayBuffer(data)
|
||||||
return temporaryFloat32Array
|
return tmpFloat32Array
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemporaryInt32Array returns a Int32Array whose length is at least minLength.
|
// tmpInt32ArrayFromInt32Slice returns a Int32Array whose length is at least minLength.
|
||||||
// Be careful that the length can exceed the given minLength.
|
// Be careful that the length can exceed the given minLength.
|
||||||
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
// data must be a slice of a numeric type for initialization, or nil if you don't need initialization.
|
||||||
func TemporaryInt32Array(minLength int, data []int32) js.Value {
|
func tmpInt32ArrayFromInt32Slice(minLength int, data []int32) js.Value {
|
||||||
ensureTemporaryArrayBufferSize(minLength * 4)
|
ensureTemporaryArrayBufferSize(minLength * 4)
|
||||||
copySliceToTemporaryArrayBuffer(data)
|
copySliceToTemporaryArrayBuffer(data)
|
||||||
return temporaryInt32Array
|
return tmpInt32Array
|
||||||
}
|
}
|
@ -17,8 +17,6 @@ package gl
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type defaultContext struct {
|
type defaultContext struct {
|
||||||
@ -288,7 +286,7 @@ func (c *defaultContext) BufferInit(target uint32, size int, usage uint32) {
|
|||||||
|
|
||||||
func (c *defaultContext) BufferSubData(target uint32, offset int, data []byte) {
|
func (c *defaultContext) BufferSubData(target uint32, offset int, data []byte) {
|
||||||
l := len(data)
|
l := len(data)
|
||||||
arr := jsutil.TemporaryUint8ArrayFromUint8Slice(l, data)
|
arr := tmpUint8ArrayFromUint8Slice(l, data)
|
||||||
c.fnBufferSubData.Invoke(target, offset, arr, 0, l)
|
c.fnBufferSubData.Invoke(target, offset, arr, 0, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +492,7 @@ func (c *defaultContext) ReadPixels(dst []byte, x int32, y int32, width int32, h
|
|||||||
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, 0)
|
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p := jsutil.TemporaryUint8ArrayFromUint8Slice(len(dst), nil)
|
p := tmpUint8ArrayFromUint8Slice(len(dst), nil)
|
||||||
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, p)
|
c.fnReadPixels.Invoke(x, y, width, height, format, xtype, p)
|
||||||
js.CopyBytesToGo(dst, p)
|
js.CopyBytesToGo(dst, p)
|
||||||
}
|
}
|
||||||
@ -531,7 +529,7 @@ func (c *defaultContext) TexParameteri(target uint32, pname uint32, param int32)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {
|
func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) {
|
||||||
arr := jsutil.TemporaryUint8ArrayFromUint8Slice(len(pixels), pixels)
|
arr := tmpUint8ArrayFromUint8Slice(len(pixels), pixels)
|
||||||
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
// void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
|
||||||
// GLsizei width, GLsizei height,
|
// GLsizei width, GLsizei height,
|
||||||
// GLenum format, GLenum type, ArrayBufferView pixels, srcOffset);
|
// GLenum format, GLenum type, ArrayBufferView pixels, srcOffset);
|
||||||
@ -540,7 +538,7 @@ func (c *defaultContext) TexSubImage2D(target uint32, level int32, xoffset int32
|
|||||||
|
|
||||||
func (c *defaultContext) Uniform1fv(location int32, value []float32) {
|
func (c *defaultContext) Uniform1fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniform1fv.Invoke(l, arr, 0, len(value))
|
c.fnUniform1fv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -551,61 +549,61 @@ func (c *defaultContext) Uniform1i(location int32, v0 int32) {
|
|||||||
|
|
||||||
func (c *defaultContext) Uniform1iv(location int32, value []int32) {
|
func (c *defaultContext) Uniform1iv(location int32, value []int32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||||
c.fnUniform1iv.Invoke(l, arr, 0, len(value))
|
c.fnUniform1iv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) Uniform2fv(location int32, value []float32) {
|
func (c *defaultContext) Uniform2fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniform2fv.Invoke(l, arr, 0, len(value))
|
c.fnUniform2fv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) Uniform2iv(location int32, value []int32) {
|
func (c *defaultContext) Uniform2iv(location int32, value []int32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||||
c.fnUniform2iv.Invoke(l, arr, 0, len(value))
|
c.fnUniform2iv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) Uniform3fv(location int32, value []float32) {
|
func (c *defaultContext) Uniform3fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniform3fv.Invoke(l, arr, 0, len(value))
|
c.fnUniform3fv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) Uniform3iv(location int32, value []int32) {
|
func (c *defaultContext) Uniform3iv(location int32, value []int32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||||
c.fnUniform3iv.Invoke(l, arr, 0, len(value))
|
c.fnUniform3iv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) Uniform4fv(location int32, value []float32) {
|
func (c *defaultContext) Uniform4fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniform4fv.Invoke(l, arr, 0, len(value))
|
c.fnUniform4fv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) Uniform4iv(location int32, value []int32) {
|
func (c *defaultContext) Uniform4iv(location int32, value []int32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryInt32Array(len(value), value)
|
arr := tmpInt32ArrayFromInt32Slice(len(value), value)
|
||||||
c.fnUniform4iv.Invoke(l, arr, 0, len(value))
|
c.fnUniform4iv.Invoke(l, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) UniformMatrix2fv(location int32, value []float32) {
|
func (c *defaultContext) UniformMatrix2fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniformMatrix2fv.Invoke(l, false, arr, 0, len(value))
|
c.fnUniformMatrix2fv.Invoke(l, false, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) UniformMatrix3fv(location int32, value []float32) {
|
func (c *defaultContext) UniformMatrix3fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniformMatrix3fv.Invoke(l, false, arr, 0, len(value))
|
c.fnUniformMatrix3fv.Invoke(l, false, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) UniformMatrix4fv(location int32, value []float32) {
|
func (c *defaultContext) UniformMatrix4fv(location int32, value []float32) {
|
||||||
l := c.getUniformLocation(location)
|
l := c.getUniformLocation(location)
|
||||||
arr := jsutil.TemporaryFloat32Array(len(value), value)
|
arr := tmpFloat32ArrayFromFloat32Slice(len(value), value)
|
||||||
c.fnUniformMatrix4fv.Invoke(l, false, arr, 0, len(value))
|
c.fnUniformMatrix4fv.Invoke(l, false, arr, 0, len(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,8 +38,7 @@ func (c *defaultContext) init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use multiple %w-s as of Go 1.20
|
return fmt.Errorf("gl: failed to load: OpenGL.framework: %w, OpenGLES.framework: %w", errGL, errGLES)
|
||||||
return fmt.Errorf("gl: failed to load: OpenGL.framework: %v, OpenGLES.framework: %v", errGL, errGLES)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
package gl
|
package gl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ebitengine/purego"
|
"github.com/ebitengine/purego"
|
||||||
)
|
)
|
||||||
@ -29,8 +29,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *defaultContext) init() error {
|
func (c *defaultContext) init() error {
|
||||||
// TODO: Use multiple %w-s as of Go 1.20.
|
var errs []error
|
||||||
var errors []string
|
|
||||||
|
|
||||||
// Try OpenGL ES first. Some machines like Android and Raspberry Pi might work only with OpenGL ES.
|
// Try OpenGL ES first. Some machines like Android and Raspberry Pi might work only with OpenGL ES.
|
||||||
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
|
for _, name := range []string{"libGLESv2.so", "libGLESv2.so.2", "libGLESv2.so.1", "libGLESv2.so.0"} {
|
||||||
@ -40,7 +39,7 @@ func (c *defaultContext) init() error {
|
|||||||
c.isES = true
|
c.isES = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
errs = append(errs, fmt.Errorf("gl: Dlopen failed: name: %s: %w", name, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try OpenGL next.
|
// Try OpenGL next.
|
||||||
@ -54,10 +53,11 @@ func (c *defaultContext) init() error {
|
|||||||
libGL = lib
|
libGL = lib
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
errors = append(errors, fmt.Sprintf("%s: %v", name, err))
|
errs = append(errs, fmt.Errorf("gl: Dlopen failed: name: %s: %w", name, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so: %s", strings.Join(errors, ", "))
|
errs = append([]error{fmt.Errorf("gl: failed to load libGL.so and libGLESv2.so: ")}, errs...)
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
func (c *defaultContext) getProcAddress(name string) (uintptr, error) {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package jsutil
|
package gl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
@ -24,7 +24,7 @@ func copyUint8SliceToTemporaryArrayBuffer(src []uint8) {
|
|||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
js.CopyBytesToJS(temporaryUint8Array, src)
|
js.CopyBytesToJS(tmpUint8Array, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
type numeric interface {
|
type numeric interface {
|
||||||
@ -35,6 +35,6 @@ func copySliceToTemporaryArrayBuffer[T numeric](src []T) {
|
|||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
js.CopyBytesToJS(temporaryUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*int(unsafe.Sizeof(T(0)))))
|
js.CopyBytesToJS(tmpUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*int(unsafe.Sizeof(T(0)))))
|
||||||
runtime.KeepAlive(src)
|
runtime.KeepAlive(src)
|
||||||
}
|
}
|
@ -1,19 +0,0 @@
|
|||||||
// Copyright 2019 The Ebiten Authors
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// This can be compiled in non-JS environments to avoid a mysterious error: 'no Go source files'
|
|
||||||
// See https://travis-ci.org/hajimehoshi/ebiten/builds/603539948
|
|
||||||
|
|
||||||
// Package jsutil offers utility functions for Wasm.
|
|
||||||
package jsutil
|
|
@ -41,7 +41,12 @@ type Mipmap struct {
|
|||||||
height int
|
height int
|
||||||
imageType atlas.ImageType
|
imageType atlas.ImageType
|
||||||
orig *buffered.Image
|
orig *buffered.Image
|
||||||
imgs map[int]*buffered.Image
|
imgs map[int]imageWithDirtyFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
type imageWithDirtyFlag struct {
|
||||||
|
img *buffered.Image
|
||||||
|
dirty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(width, height int, imageType atlas.ImageType) *Mipmap {
|
func New(width, height int, imageType atlas.ImageType) *Mipmap {
|
||||||
@ -59,7 +64,14 @@ func (m *Mipmap) DumpScreenshot(graphicsDriver graphicsdriver.Graphics, name str
|
|||||||
|
|
||||||
func (m *Mipmap) WritePixels(pix []byte, region image.Rectangle) {
|
func (m *Mipmap) WritePixels(pix []byte, region image.Rectangle) {
|
||||||
m.orig.WritePixels(pix, region)
|
m.orig.WritePixels(pix, region)
|
||||||
m.deallocateMipmaps()
|
m.markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mipmap) markDirty() {
|
||||||
|
for i, img := range m.imgs {
|
||||||
|
img.dirty = true
|
||||||
|
m.imgs[i] = img
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) (ok bool, err error) {
|
func (m *Mipmap) ReadPixels(graphicsDriver graphicsdriver.Graphics, pixels []byte, region image.Rectangle) (ok bool, err error) {
|
||||||
@ -125,14 +137,17 @@ func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderSrcImageCount]*Mipmap, verti
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
m.orig.DrawTriangles(imgs, vertices, indices, blend, dstRegion, srcRegions, shader, uniforms, fillRule, hint)
|
||||||
m.deallocateMipmaps()
|
m.markDirty()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mipmap) setImg(level int, img *buffered.Image) {
|
func (m *Mipmap) setImg(level int, img *buffered.Image) {
|
||||||
if m.imgs == nil {
|
if m.imgs == nil {
|
||||||
m.imgs = map[int]*buffered.Image{}
|
m.imgs = map[int]imageWithDirtyFlag{}
|
||||||
|
}
|
||||||
|
m.imgs[level] = imageWithDirtyFlag{
|
||||||
|
img: img,
|
||||||
|
dirty: false,
|
||||||
}
|
}
|
||||||
m.imgs[level] = img
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mipmap) level(level int) *buffered.Image {
|
func (m *Mipmap) level(level int) *buffered.Image {
|
||||||
@ -144,54 +159,63 @@ func (m *Mipmap) level(level int) *buffered.Image {
|
|||||||
panic("mipmap: mipmap images for a screen image is not implemented yet")
|
panic("mipmap: mipmap images for a screen image is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
if img, ok := m.imgs[level]; ok {
|
img, ok := m.imgs[level]
|
||||||
return img
|
if ok && !img.dirty {
|
||||||
|
return img.img
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var srcW, srcH int
|
||||||
var src *buffered.Image
|
var src *buffered.Image
|
||||||
vs := make([]float32, 4*graphics.VertexFloatCount)
|
vs := make([]float32, 4*graphics.VertexFloatCount)
|
||||||
switch {
|
switch {
|
||||||
case level == 1:
|
case level == 1:
|
||||||
src = m.orig
|
src = m.orig
|
||||||
graphics.QuadVerticesFromSrcAndMatrix(vs, 0, 0, float32(m.width), float32(m.height), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
srcW = m.width
|
||||||
|
srcH = m.height
|
||||||
case level > 1:
|
case level > 1:
|
||||||
src = m.level(level - 1)
|
src = m.level(level - 1)
|
||||||
if src == nil {
|
if src == nil {
|
||||||
m.setImg(level, nil)
|
m.setImg(level, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
w := sizeForLevel(m.width, level-1)
|
srcW = sizeForLevel(m.width, level-1)
|
||||||
h := sizeForLevel(m.height, level-1)
|
srcH = sizeForLevel(m.height, level-1)
|
||||||
graphics.QuadVerticesFromSrcAndMatrix(vs, 0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("mipmap: invalid level: %d", level))
|
panic(fmt.Sprintf("mipmap: invalid level: %d", level))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
graphics.QuadVerticesFromSrcAndMatrix(vs, 0, 0, float32(srcW), float32(srcH), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1)
|
||||||
|
|
||||||
is := graphics.QuadIndices()
|
is := graphics.QuadIndices()
|
||||||
|
|
||||||
w2 := sizeForLevel(m.width, level)
|
dstW := sizeForLevel(m.width, level)
|
||||||
h2 := sizeForLevel(m.height, level)
|
dstH := sizeForLevel(m.height, level)
|
||||||
if w2 == 0 || h2 == 0 {
|
if dstW == 0 || dstH == 0 {
|
||||||
m.setImg(level, nil)
|
m.setImg(level, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// buffered.NewImage panics with a too big size when actual allocation happens.
|
// buffered.NewImage panics with a too big size when actual allocation happens.
|
||||||
// 4096 should be a safe size in most environments (#1399).
|
// 4096 should be a safe size in most environments (#1399).
|
||||||
// Unfortunately a precise max image size cannot be obtained here since this requires GPU access.
|
// Unfortunately a precise max image size cannot be obtained here since this requires GPU access.
|
||||||
if w2 > 4096 || h2 > 4096 {
|
if dstW > 4096 || dstH > 4096 {
|
||||||
m.setImg(level, nil)
|
m.setImg(level, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s := buffered.NewImage(w2, h2, m.imageType)
|
var s *buffered.Image
|
||||||
|
if img.img != nil {
|
||||||
|
// As s is overwritten, this doesn't have to be cleared.
|
||||||
|
s = img.img
|
||||||
|
} else {
|
||||||
|
s = buffered.NewImage(dstW, dstH, m.imageType)
|
||||||
|
}
|
||||||
|
|
||||||
dstRegion := image.Rect(0, 0, w2, h2)
|
dstRegion := image.Rect(0, 0, dstW, dstH)
|
||||||
w := sizeForLevel(m.width, level-1)
|
srcRegion := image.Rect(0, 0, srcW, srcH)
|
||||||
h := sizeForLevel(m.height, level-1)
|
|
||||||
srcRegion := image.Rect(0, 0, w, h)
|
|
||||||
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
s.DrawTriangles([graphics.ShaderSrcImageCount]*buffered.Image{src}, vs, is, graphicsdriver.BlendCopy, dstRegion, [graphics.ShaderSrcImageCount]image.Rectangle{srcRegion}, atlas.LinearFilterShader, nil, graphicsdriver.FillRuleFillAll, restorable.HintOverwriteDstRegion)
|
||||||
m.setImg(level, s)
|
m.setImg(level, s)
|
||||||
|
|
||||||
return m.imgs[level]
|
return m.imgs[level].img
|
||||||
}
|
}
|
||||||
|
|
||||||
func sizeForLevel(x int, level int) int {
|
func sizeForLevel(x int, level int) int {
|
||||||
@ -205,19 +229,16 @@ func sizeForLevel(x int, level int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mipmap) Deallocate() {
|
func (m *Mipmap) Deallocate() {
|
||||||
m.deallocateMipmaps()
|
|
||||||
m.orig.Deallocate()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Mipmap) deallocateMipmaps() {
|
|
||||||
for _, img := range m.imgs {
|
for _, img := range m.imgs {
|
||||||
if img != nil {
|
if img.img == nil {
|
||||||
img.Deallocate()
|
continue
|
||||||
}
|
}
|
||||||
|
img.img.Deallocate()
|
||||||
}
|
}
|
||||||
for k := range m.imgs {
|
for k := range m.imgs {
|
||||||
delete(m.imgs, k)
|
delete(m.imgs, k)
|
||||||
}
|
}
|
||||||
|
m.orig.Deallocate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// mipmapLevel returns an appropriate mipmap level for the given distance.
|
// mipmapLevel returns an appropriate mipmap level for the given distance.
|
||||||
|
@ -136,18 +136,6 @@ func run() error {
|
|||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
format.Node(w, fset, tree)
|
format.Node(w, fset, tree)
|
||||||
|
|
||||||
if f == "reader.go" {
|
|
||||||
// The min function was removed as of Go 1.22, but this is needed for old Go.
|
|
||||||
// TODO: Remove this when Go 1.21 is the minimum supported version.
|
|
||||||
fmt.Fprintln(w, `
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.Flush(); err != nil {
|
if err := w.Flush(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1054,10 +1054,3 @@ func DecodeConfig(r io.Reader) (image.Config, error) {
|
|||||||
func init() {
|
func init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
@ -54,7 +54,7 @@ type context struct {
|
|||||||
offscreenHeight float64
|
offscreenHeight float64
|
||||||
|
|
||||||
isOffscreenModified bool
|
isOffscreenModified bool
|
||||||
lastDrawTime time.Time
|
lastSwapBufferTime time.Time
|
||||||
|
|
||||||
skipCount int
|
skipCount int
|
||||||
|
|
||||||
@ -70,7 +70,14 @@ func newContext(game Game) *context {
|
|||||||
|
|
||||||
func (c *context) updateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
func (c *context) updateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||||
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.
|
||||||
return c.updateFrameImpl(graphicsDriver, clock.UpdateFrame(), outsideWidth, outsideHeight, deviceScaleFactor, ui, false)
|
needsSwapBuffers, err := c.updateFrameImpl(graphicsDriver, clock.UpdateFrame(), outsideWidth, outsideHeight, deviceScaleFactor, ui, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.swapBuffersOrWait(needsSwapBuffers, graphicsDriver, ui.FPSMode() == FPSModeVsyncOn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface) error {
|
||||||
@ -81,33 +88,32 @@ func (c *context) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsi
|
|||||||
n = 2
|
n = 2
|
||||||
}
|
}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
if err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true); err != nil {
|
needsSwapBuffers, err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor, ui, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := c.swapBuffersOrWait(needsSwapBuffers, graphicsDriver, ui.FPSMode() == FPSModeVsyncOn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface, forceDraw bool) (err error) {
|
func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64, ui *UserInterface, forceDraw bool) (needsSwapBuffers bool, err error) {
|
||||||
// The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589)
|
// The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589)
|
||||||
// Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic.
|
// Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic.
|
||||||
if outsideWidth == 0 || outsideHeight == 0 {
|
if outsideWidth == 0 || outsideHeight == 0 {
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.FrameLogf("----\n")
|
debug.FrameLogf("----\n")
|
||||||
|
|
||||||
if err := atlas.BeginFrame(graphicsDriver); err != nil {
|
if err := atlas.BeginFrame(graphicsDriver); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err1 := atlas.EndFrame(); err1 != nil && err == nil {
|
if err1 := atlas.EndFrame(); err1 != nil && err == nil {
|
||||||
err = err1
|
needsSwapBuffers = false
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err1 := atlas.SwapBuffers(graphicsDriver); err1 != nil && err == nil {
|
|
||||||
err = err1
|
err = err1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -115,17 +121,17 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
|
|
||||||
// Flush deferred functions, like reading pixels from GPU.
|
// Flush deferred functions, like reading pixels from GPU.
|
||||||
if err := c.processFuncsInFrame(ui); err != nil {
|
if err := c.processFuncsInFrame(ui); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceUpdate can be invoked even if the context is not initialized yet (#1591).
|
// ForceUpdate can be invoked even if the context is not initialized yet (#1591).
|
||||||
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
|
if w, h := c.layoutGame(outsideWidth, outsideHeight, deviceScaleFactor); w == 0 || h == 0 {
|
||||||
return nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the input state after the layout is updated as a cursor position is affected by the layout.
|
// Update the input state after the layout is updated as a cursor position is affected by the layout.
|
||||||
if err := ui.updateInputState(); err != nil {
|
if err := ui.updateInputState(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that Update is called once before Draw so that Update can be used for initialization.
|
// Ensure that Update is called once before Draw so that Update can be used for initialization.
|
||||||
@ -143,15 +149,15 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err := hook.RunBeforeUpdateHooks(); err != nil {
|
if err := hook.RunBeforeUpdateHooks(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
if err := c.game.Update(); err != nil {
|
if err := c.game.Update(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catch the error that happened at (*Image).At.
|
// Catch the error that happened at (*Image).At.
|
||||||
if err := ui.error(); err != nil {
|
if err := ui.error(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.tick.Add(1)
|
ui.tick.Add(1)
|
||||||
@ -160,13 +166,39 @@ func (c *context) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, update
|
|||||||
// Update window icons during a frame, since an icon might be *ebiten.Image and
|
// Update window icons during a frame, since an icon might be *ebiten.Image and
|
||||||
// getting pixels from it needs to be in a frame (#1468).
|
// getting pixels from it needs to be in a frame (#1468).
|
||||||
if err := ui.updateIconIfNeeded(); err != nil {
|
if err := ui.updateIconIfNeeded(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the game.
|
// Draw the game.
|
||||||
if err := c.drawGame(graphicsDriver, ui, forceDraw); err != nil {
|
return c.drawGame(graphicsDriver, ui, forceDraw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) swapBuffersOrWait(needsSwapBuffers bool, graphicsDriver graphicsdriver.Graphics, vsyncEnabled bool) error {
|
||||||
|
now := time.Now()
|
||||||
|
defer func() {
|
||||||
|
c.lastSwapBufferTime = now
|
||||||
|
}()
|
||||||
|
|
||||||
|
if needsSwapBuffers {
|
||||||
|
if err := atlas.SwapBuffers(graphicsDriver); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var waitTime time.Duration
|
||||||
|
if !needsSwapBuffers {
|
||||||
|
// When swapping buffers is skipped and Draw is called too early, sleep for a while to suppress CPU usages (#2890).
|
||||||
|
waitTime = time.Second / 60
|
||||||
|
} else if vsyncEnabled {
|
||||||
|
// In some environments, e.g. Linux on Parallels, SwapBuffers doesn't wait for the vsync (#2952).
|
||||||
|
// In the case when the display has high refresh rates like 240 [Hz], the wait time should be small.
|
||||||
|
waitTime = time.Millisecond
|
||||||
|
}
|
||||||
|
if waitTime > 0 {
|
||||||
|
if delta := waitTime - now.Sub(c.lastSwapBufferTime); delta > 0 {
|
||||||
|
time.Sleep(delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -179,7 +211,7 @@ func (c *context) newOffscreenImage(w, h int) *Image {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) error {
|
func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInterface, forceDraw bool) (needSwapBuffers bool, err error) {
|
||||||
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() {
|
if (c.offscreen.imageType == atlas.ImageTypeVolatile) != ui.IsScreenClearedEveryFrame() {
|
||||||
w, h := c.offscreen.width, c.offscreen.height
|
w, h := c.offscreen.width, c.offscreen.height
|
||||||
c.offscreen.Deallocate()
|
c.offscreen.Deallocate()
|
||||||
@ -197,7 +229,7 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.game.DrawOffscreen(); err != nil {
|
if err := c.game.DrawOffscreen(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxSkipCount = 4
|
const maxSkipCount = 4
|
||||||
@ -210,12 +242,10 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
|||||||
c.skipCount = 0
|
c.skipCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
if c.skipCount >= maxSkipCount {
|
||||||
defer func() {
|
return false, nil
|
||||||
c.lastDrawTime = now
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
if c.skipCount < maxSkipCount {
|
|
||||||
if graphicsDriver.NeedsClearingScreen() {
|
if graphicsDriver.NeedsClearingScreen() {
|
||||||
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
// This clear is needed for fullscreen mode or some mobile platforms (#622).
|
||||||
c.screen.clear()
|
c.screen.clear()
|
||||||
@ -226,12 +256,7 @@ func (c *context) drawGame(graphicsDriver graphicsdriver.Graphics, ui *UserInter
|
|||||||
// The final screen is never used as the rendering source.
|
// The final screen is never used as the rendering source.
|
||||||
// Flush its buffer here just in case.
|
// Flush its buffer here just in case.
|
||||||
c.screen.flushBufferIfNeeded()
|
c.screen.flushBufferIfNeeded()
|
||||||
} else if delta := time.Second/60 - now.Sub(c.lastDrawTime); delta > 0 {
|
return true, nil
|
||||||
// When swapping buffers is skipped and Draw is called too early, sleep for a while to suppress CPU usages (#2890).
|
|
||||||
time.Sleep(delta)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFactor float64) (int, int) {
|
||||||
@ -240,8 +265,13 @@ func (c *context) layoutGame(outsideWidth, outsideHeight float64, deviceScaleFac
|
|||||||
panic("ui: Layout must return positive numbers")
|
panic("ui: Layout must return positive numbers")
|
||||||
}
|
}
|
||||||
|
|
||||||
c.screenWidth = outsideWidth * deviceScaleFactor
|
screenWidth := outsideWidth * deviceScaleFactor
|
||||||
c.screenHeight = outsideHeight * deviceScaleFactor
|
screenHeight := outsideHeight * deviceScaleFactor
|
||||||
|
if c.screenWidth != screenWidth || c.screenHeight != screenHeight {
|
||||||
|
c.skipCount = 0
|
||||||
|
}
|
||||||
|
c.screenWidth = screenWidth
|
||||||
|
c.screenHeight = screenHeight
|
||||||
c.offscreenWidth = owf
|
c.offscreenWidth = owf
|
||||||
c.offscreenHeight = ohf
|
c.offscreenHeight = ohf
|
||||||
|
|
||||||
|
@ -18,15 +18,19 @@ package ui
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
// Basically same as:
|
// The following JNI code works as this pseudo Java code:
|
||||||
//
|
//
|
||||||
// WindowService windowService = context.getSystemService(Context.WINDOW_SERVICE);
|
// WindowService windowService = context.getSystemService(Context.WINDOW_SERVICE);
|
||||||
// Display display = windowManager.getDefaultDisplay();
|
// Display display = windowManager.getDefaultDisplay();
|
||||||
// DisplayMetrics displayMetrics = new DisplayMetrics();
|
// DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||||
// display.getRealMetrics(displayMetrics);
|
// display.getRealMetrics(displayMetrics);
|
||||||
// this.deviceScale = displayMetrics.density;
|
// return displayMetrics.widthPixels, displayMetrics.heightPixels, displayMetrics.density;
|
||||||
//
|
//
|
||||||
static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
static void displayInfo(int* width, int* height, float* scale, uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
||||||
|
*width = 0;
|
||||||
|
*height = 0;
|
||||||
|
*scale = 1;
|
||||||
|
|
||||||
JavaVM* vm = (JavaVM*)java_vm;
|
JavaVM* vm = (JavaVM*)java_vm;
|
||||||
JNIEnv* env = (JNIEnv*)jni_env;
|
JNIEnv* env = (JNIEnv*)jni_env;
|
||||||
jobject context = (jobject)ctx;
|
jobject context = (jobject)ctx;
|
||||||
@ -64,7 +68,15 @@ static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
|||||||
env, display,
|
env, display,
|
||||||
(*env)->GetMethodID(env, android_view_Display, "getRealMetrics", "(Landroid/util/DisplayMetrics;)V"),
|
(*env)->GetMethodID(env, android_view_Display, "getRealMetrics", "(Landroid/util/DisplayMetrics;)V"),
|
||||||
displayMetrics);
|
displayMetrics);
|
||||||
const float density =
|
*width =
|
||||||
|
(*env)->GetIntField(
|
||||||
|
env, displayMetrics,
|
||||||
|
(*env)->GetFieldID(env, android_util_DisplayMetrics, "widthPixels", "I"));
|
||||||
|
*height =
|
||||||
|
(*env)->GetIntField(
|
||||||
|
env, displayMetrics,
|
||||||
|
(*env)->GetFieldID(env, android_util_DisplayMetrics, "heightPixels", "I"));
|
||||||
|
*scale =
|
||||||
(*env)->GetFloatField(
|
(*env)->GetFloatField(
|
||||||
env, displayMetrics,
|
env, displayMetrics,
|
||||||
(*env)->GetFieldID(env, android_util_DisplayMetrics, "density", "F"));
|
(*env)->GetFieldID(env, android_util_DisplayMetrics, "density", "F"));
|
||||||
@ -78,15 +90,12 @@ static float deviceScale(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
|
|||||||
(*env)->DeleteLocalRef(env, windowManager);
|
(*env)->DeleteLocalRef(env, windowManager);
|
||||||
(*env)->DeleteLocalRef(env, display);
|
(*env)->DeleteLocalRef(env, display);
|
||||||
(*env)->DeleteLocalRef(env, displayMetrics);
|
(*env)->DeleteLocalRef(env, displayMetrics);
|
||||||
|
|
||||||
return density;
|
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ebitengine/gomobile/app"
|
"github.com/ebitengine/gomobile/app"
|
||||||
|
|
||||||
@ -119,18 +128,27 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
|
|||||||
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
return nil, errors.New("ui: PlayStation 5 is not supported in this environment")
|
||||||
}
|
}
|
||||||
|
|
||||||
func deviceScaleFactorImpl() float64 {
|
|
||||||
var s float64
|
|
||||||
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
|
||||||
// TODO: This might be crash when this is called from init(). How can we detect this?
|
|
||||||
s = float64(C.deviceScale(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx)))
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
panic(fmt.Sprintf("devicescale: error %v", err))
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func dipToNativePixels(x float64, scale float64) float64 {
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
return x * scale
|
return x * scale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipFromNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x / scale
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserInterface) displayInfo() (int, int, float64, bool) {
|
||||||
|
var cWidth, cHeight C.int
|
||||||
|
var cScale C.float
|
||||||
|
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||||
|
C.displayInfo(&cWidth, &cHeight, &cScale, C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
// JVM is not ready yet.
|
||||||
|
// TODO: Fix gomobile to detect the error type for this case.
|
||||||
|
return 0, 0, 1, false
|
||||||
|
}
|
||||||
|
scale := float64(cScale)
|
||||||
|
width := int(dipFromNativePixels(float64(cWidth), scale))
|
||||||
|
height := int(dipFromNativePixels(float64(cHeight), scale))
|
||||||
|
return width, height, scale, true
|
||||||
|
}
|
||||||
|
@ -81,6 +81,8 @@ type userInterfaceImpl struct {
|
|||||||
initWindowMaximized bool
|
initWindowMaximized bool
|
||||||
initWindowMousePassthrough bool
|
initWindowMousePassthrough bool
|
||||||
|
|
||||||
|
initUnfocused bool
|
||||||
|
|
||||||
// bufferOnceSwapped must be accessed from the main thread.
|
// bufferOnceSwapped must be accessed from the main thread.
|
||||||
bufferOnceSwapped bool
|
bufferOnceSwapped bool
|
||||||
|
|
||||||
@ -1119,6 +1121,7 @@ func (u *UserInterface) initOnMainThread(options *RunOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.initUnfocused = options.InitUnfocused
|
||||||
focused := glfw.True
|
focused := glfw.True
|
||||||
if options.InitUnfocused {
|
if options.InitUnfocused {
|
||||||
focused = glfw.False
|
focused = glfw.False
|
||||||
@ -1320,9 +1323,11 @@ func (u *UserInterface) update() (float64, float64, error) {
|
|||||||
if err = u.window.Show(); err != nil {
|
if err = u.window.Show(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !u.initUnfocused {
|
||||||
if err = u.window.Focus(); err != nil {
|
if err = u.window.Focus(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
||||||
return
|
return
|
||||||
@ -1383,7 +1388,9 @@ func (u *UserInterface) update() (float64, float64, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for !u.isRunnableOnUnfocused() {
|
// If isRunnableOnUnfocused is false and the window is not focused, wait here.
|
||||||
|
// For the first update, skip this check as the window might not be seen yet in some environments like ChromeOS (#3091).
|
||||||
|
for !u.isRunnableOnUnfocused() && u.bufferOnceSwapped {
|
||||||
// In the initial state on macOS, the window is not shown (#2620).
|
// In the initial state on macOS, the window is not shown (#2620).
|
||||||
visible, err := u.window.GetAttrib(glfw.Visible)
|
visible, err := u.window.GetAttrib(glfw.Visible)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -19,8 +19,43 @@ package ui
|
|||||||
//
|
//
|
||||||
// #import <UIKit/UIKit.h>
|
// #import <UIKit/UIKit.h>
|
||||||
//
|
//
|
||||||
// static double devicePixelRatio() {
|
// static void displayInfoOnMainThread(float* width, float* height, float* scale, UIView* view) {
|
||||||
// return [[UIScreen mainScreen] nativeScale];
|
// *width = 0;
|
||||||
|
// *height = 0;
|
||||||
|
// *scale = 1;
|
||||||
|
// UIWindow* window = view.window;
|
||||||
|
// if (!window) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// UIWindowScene* scene = window.windowScene;
|
||||||
|
// if (!scene) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// CGRect bounds = scene.screen.bounds;
|
||||||
|
// *width = bounds.size.width;
|
||||||
|
// *height = bounds.size.height;
|
||||||
|
// *scale = scene.screen.nativeScale;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// static void displayInfo(float* width, float* height, float* scale, uintptr_t viewPtr) {
|
||||||
|
// *width = 0;
|
||||||
|
// *height = 0;
|
||||||
|
// *scale = 1;
|
||||||
|
// if (!viewPtr) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// UIView* view = (__bridge UIView*)(void*)viewPtr;
|
||||||
|
// if ([NSThread isMainThread]) {
|
||||||
|
// displayInfoOnMainThread(width, height, scale, view);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// __block float w, h, s;
|
||||||
|
// dispatch_sync(dispatch_get_main_queue(), ^{
|
||||||
|
// displayInfoOnMainThread(&w, &h, &s, view);
|
||||||
|
// });
|
||||||
|
// *width = w;
|
||||||
|
// *height = h;
|
||||||
|
// *scale = s;
|
||||||
// }
|
// }
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@ -66,6 +101,7 @@ func (*graphicsDriverCreatorImpl) newPlayStation5() (graphicsdriver.Graphics, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) SetUIView(uiview uintptr) error {
|
func (u *UserInterface) SetUIView(uiview uintptr) error {
|
||||||
|
u.uiView.Store(uiview)
|
||||||
select {
|
select {
|
||||||
case err := <-u.errCh:
|
case err := <-u.errCh:
|
||||||
return err
|
return err
|
||||||
@ -89,11 +125,24 @@ func (u *UserInterface) IsGL() (bool, error) {
|
|||||||
return u.GraphicsLibrary() == GraphicsLibraryOpenGL, nil
|
return u.GraphicsLibrary() == GraphicsLibraryOpenGL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deviceScaleFactorImpl() float64 {
|
|
||||||
// TODO: Can this be called from non-main threads?
|
|
||||||
return float64(C.devicePixelRatio())
|
|
||||||
}
|
|
||||||
|
|
||||||
func dipToNativePixels(x float64, scale float64) float64 {
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipFromNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UserInterface) displayInfo() (int, int, float64, bool) {
|
||||||
|
view := u.uiView.Load()
|
||||||
|
if view == 0 {
|
||||||
|
return 0, 0, 1, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var cWidth, cHeight, cScale C.float
|
||||||
|
C.displayInfo(&cWidth, &cHeight, &cScale, C.uintptr_t(view))
|
||||||
|
scale := float64(cScale)
|
||||||
|
width := int(dipFromNativePixels(float64(cWidth), scale))
|
||||||
|
height := int(dipFromNativePixels(float64(cHeight), scale))
|
||||||
|
return width, height, scale, true
|
||||||
|
}
|
||||||
|
@ -539,6 +539,10 @@ func (u *UserInterface) init() error {
|
|||||||
}))
|
}))
|
||||||
document.Call("addEventListener", "pointerlockerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
document.Call("addEventListener", "pointerlockerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
js.Global().Get("console").Call("error", "pointerlockerror event is fired. 'sandbox=\"allow-pointer-lock\"' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change.")
|
js.Global().Get("console").Call("error", "pointerlockerror event is fired. 'sandbox=\"allow-pointer-lock\"' might be required at an iframe. This function on browsers must be called as a result of a gestural interaction or orientation change.")
|
||||||
|
if u.cursorMode == CursorModeCaptured {
|
||||||
|
u.recoverCursorMode()
|
||||||
|
}
|
||||||
|
u.recoverCursorPosition()
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
document.Call("addEventListener", "fullscreenerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
document.Call("addEventListener", "fullscreenerror", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
@ -104,6 +104,9 @@ type userInterfaceImpl struct {
|
|||||||
strictContextRestoration atomic.Bool
|
strictContextRestoration atomic.Bool
|
||||||
strictContextRestorationOnce sync.Once
|
strictContextRestorationOnce sync.Once
|
||||||
|
|
||||||
|
// uiView is used only on iOS.
|
||||||
|
uiView atomic.Uintptr
|
||||||
|
|
||||||
m sync.RWMutex
|
m sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,8 +268,10 @@ func (u *UserInterface) Window() Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Monitor struct {
|
type Monitor struct {
|
||||||
|
width int
|
||||||
|
height int
|
||||||
deviceScaleFactor float64
|
deviceScaleFactor float64
|
||||||
deviceScaleFactorOnce sync.Once
|
inited atomic.Bool
|
||||||
|
|
||||||
m sync.Mutex
|
m sync.Mutex
|
||||||
}
|
}
|
||||||
@ -277,22 +282,35 @@ func (m *Monitor) Name() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) DeviceScaleFactor() float64 {
|
func (m *Monitor) ensureInit() {
|
||||||
|
if m.inited.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m.m.Lock()
|
m.m.Lock()
|
||||||
defer m.m.Unlock()
|
defer m.m.Unlock()
|
||||||
|
// Re-check the state since the state might be changed while locking.
|
||||||
|
if m.inited.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
width, height, scale, ok := theUI.displayInfo()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.width = width
|
||||||
|
m.height = height
|
||||||
|
m.deviceScaleFactor = scale
|
||||||
|
m.inited.Store(true)
|
||||||
|
}
|
||||||
|
|
||||||
// The device scale factor can be obtained after the main function starts, especially on Android.
|
func (m *Monitor) DeviceScaleFactor() float64 {
|
||||||
// Initialize this lazily.
|
m.ensureInit()
|
||||||
m.deviceScaleFactorOnce.Do(func() {
|
|
||||||
// Assume that the device scale factor never changes on mobiles.
|
|
||||||
m.deviceScaleFactor = deviceScaleFactorImpl()
|
|
||||||
})
|
|
||||||
return m.deviceScaleFactor
|
return m.deviceScaleFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monitor) Size() (int, int) {
|
func (m *Monitor) Size() (int, int) {
|
||||||
// TODO: Return a valid value.
|
m.ensureInit()
|
||||||
return 0, 0
|
return m.width, m.height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
|
func (u *UserInterface) AppendMonitors(mons []*Monitor) []*Monitor {
|
||||||
|
@ -32,8 +32,7 @@ func (m *MonitorType) Name() string {
|
|||||||
// DeviceScaleFactor returns a meaningful value on high-DPI display environment,
|
// DeviceScaleFactor returns a meaningful value on high-DPI display environment,
|
||||||
// otherwise DeviceScaleFactor returns 1.
|
// otherwise DeviceScaleFactor returns 1.
|
||||||
//
|
//
|
||||||
// DeviceScaleFactor might panic on init function on some devices like Android.
|
// On mobiles, DeviceScaleFactor returns 1 before the game starts e.g. in init functions.
|
||||||
// Then, it is not recommended to call DeviceScaleFactor from init functions.
|
|
||||||
func (m *MonitorType) DeviceScaleFactor() float64 {
|
func (m *MonitorType) DeviceScaleFactor() float64 {
|
||||||
return (*ui.Monitor)(m).DeviceScaleFactor()
|
return (*ui.Monitor)(m).DeviceScaleFactor()
|
||||||
}
|
}
|
||||||
@ -42,7 +41,7 @@ func (m *MonitorType) DeviceScaleFactor() float64 {
|
|||||||
// This is the same as the screen size in fullscreen mode.
|
// This is the same as the screen size in fullscreen mode.
|
||||||
// The returned value can be given to SetSize function if the perfectly fit fullscreen is needed.
|
// The returned value can be given to SetSize function if the perfectly fit fullscreen is needed.
|
||||||
//
|
//
|
||||||
// On mobiles, Size returns (0, 0) so far.
|
// On mobiles, Size returns (0, 0) before the game starts e.g. in init functions.
|
||||||
//
|
//
|
||||||
// Size's use cases are limited. If you are making a fullscreen application, you can use RunGame and
|
// Size's use cases are limited. If you are making a fullscreen application, you can use RunGame and
|
||||||
// the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's
|
// the Game interface's Layout function instead. If you are making a not-fullscreen application but the application's
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// A MouseButton represents a mouse button.
|
// A MouseButton represents a mouse button.
|
||||||
type MouseButton = ui.MouseButton
|
type MouseButton int
|
||||||
|
|
||||||
// MouseButtons
|
// MouseButtons
|
||||||
const (
|
const (
|
||||||
@ -27,10 +27,10 @@ const (
|
|||||||
MouseButtonMiddle MouseButton = MouseButton1
|
MouseButtonMiddle MouseButton = MouseButton1
|
||||||
MouseButtonRight MouseButton = MouseButton2
|
MouseButtonRight MouseButton = MouseButton2
|
||||||
|
|
||||||
MouseButton0 MouseButton = ui.MouseButton0
|
MouseButton0 MouseButton = MouseButton(ui.MouseButton0)
|
||||||
MouseButton1 MouseButton = ui.MouseButton1
|
MouseButton1 MouseButton = MouseButton(ui.MouseButton1)
|
||||||
MouseButton2 MouseButton = ui.MouseButton2
|
MouseButton2 MouseButton = MouseButton(ui.MouseButton2)
|
||||||
MouseButton3 MouseButton = ui.MouseButton3
|
MouseButton3 MouseButton = MouseButton(ui.MouseButton3)
|
||||||
MouseButton4 MouseButton = ui.MouseButton4
|
MouseButton4 MouseButton = MouseButton(ui.MouseButton4)
|
||||||
MouseButtonMax MouseButton = MouseButton4
|
MouseButtonMax MouseButton = MouseButton4
|
||||||
)
|
)
|
||||||
|
79
run.go
79
run.go
@ -297,27 +297,6 @@ type RunGameOptions struct {
|
|||||||
|
|
||||||
// X11InstanceName is an instance name in the ICCCM WM_CLASS window property.
|
// X11InstanceName is an instance name in the ICCCM WM_CLASS window property.
|
||||||
X11InstanceName string
|
X11InstanceName string
|
||||||
|
|
||||||
// StrictContextRestration indicates whether the context lost should be restored strictly by Ebitengine or not.
|
|
||||||
//
|
|
||||||
// StrictContextRestration is available only on Android. Otherwise, StrictContextRestration is ignored.
|
|
||||||
//
|
|
||||||
// In Android, Ebitengien uses `GLSurfaceView`'s `setPreserveEGLContextOnPause(true)`.
|
|
||||||
// This works in most cases, but it is still possible that the context is lost in some minor cases.
|
|
||||||
//
|
|
||||||
// When StrictContextRestration is true, Ebitengine tries to restore the context more strictly
|
|
||||||
// for such minor cases.
|
|
||||||
// However, this might cause a performance issue since Ebitengine tries to keep all the information
|
|
||||||
// to restore the context.
|
|
||||||
//
|
|
||||||
// When StrictContextRestration is false, Ebitengine does nothing special to restore the context and
|
|
||||||
// relies on the OS's behavior.
|
|
||||||
//
|
|
||||||
// As a side note, especially when StrictContextRestration is false, the activity's launch mode should
|
|
||||||
// be singleInstance, or the activity no longer works correctly after the context is lost.
|
|
||||||
//
|
|
||||||
// The default (zero) value is false.
|
|
||||||
StrictContextRestration bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunGameWithOptions starts the main loop and runs the game with the specified options.
|
// RunGameWithOptions starts the main loop and runs the game with the specified options.
|
||||||
@ -405,7 +384,7 @@ func ScreenSizeInFullscreen() (int, int) {
|
|||||||
//
|
//
|
||||||
// CursorMode is concurrent-safe.
|
// CursorMode is concurrent-safe.
|
||||||
func CursorMode() CursorModeType {
|
func CursorMode() CursorModeType {
|
||||||
return ui.Get().CursorMode()
|
return CursorModeType(ui.Get().CursorMode())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCursorMode sets the render and capture mode of the mouse cursor.
|
// SetCursorMode sets the render and capture mode of the mouse cursor.
|
||||||
@ -427,25 +406,7 @@ func CursorMode() CursorModeType {
|
|||||||
//
|
//
|
||||||
// SetCursorMode is concurrent-safe.
|
// SetCursorMode is concurrent-safe.
|
||||||
func SetCursorMode(mode CursorModeType) {
|
func SetCursorMode(mode CursorModeType) {
|
||||||
ui.Get().SetCursorMode(mode)
|
ui.Get().SetCursorMode(ui.CursorMode(mode))
|
||||||
}
|
|
||||||
|
|
||||||
// CursorShape returns the current cursor shape.
|
|
||||||
//
|
|
||||||
// CursorShape returns CursorShapeDefault on mobiles.
|
|
||||||
//
|
|
||||||
// CursorShape is concurrent-safe.
|
|
||||||
func CursorShape() CursorShapeType {
|
|
||||||
return ui.Get().CursorShape()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCursorShape sets the cursor shape.
|
|
||||||
//
|
|
||||||
// If the platform doesn't implement the given shape, the default cursor shape is used.
|
|
||||||
//
|
|
||||||
// SetCursorShape is concurrent-safe.
|
|
||||||
func SetCursorShape(shape CursorShapeType) {
|
|
||||||
ui.Get().SetCursorShape(shape)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFullscreen reports whether the current mode is fullscreen or not.
|
// IsFullscreen reports whether the current mode is fullscreen or not.
|
||||||
@ -549,14 +510,14 @@ func SetVsyncEnabled(enabled bool) {
|
|||||||
// FPSModeType is a type of FPS modes.
|
// FPSModeType is a type of FPS modes.
|
||||||
//
|
//
|
||||||
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
||||||
type FPSModeType = ui.FPSModeType
|
type FPSModeType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// FPSModeVsyncOn indicates that the game tries to sync the display's refresh rate.
|
// FPSModeVsyncOn indicates that the game tries to sync the display's refresh rate.
|
||||||
// FPSModeVsyncOn is the default mode.
|
// FPSModeVsyncOn is the default mode.
|
||||||
//
|
//
|
||||||
// Deprecated: as of v2.5. Use SetVsyncEnabled(true) instead.
|
// Deprecated: as of v2.5. Use SetVsyncEnabled(true) instead.
|
||||||
FPSModeVsyncOn FPSModeType = ui.FPSModeVsyncOn
|
FPSModeVsyncOn FPSModeType = FPSModeType(ui.FPSModeVsyncOn)
|
||||||
|
|
||||||
// FPSModeVsyncOffMaximum indicates that the game doesn't sync with vsync, and
|
// FPSModeVsyncOffMaximum indicates that the game doesn't sync with vsync, and
|
||||||
// the game is updated whenever possible.
|
// the game is updated whenever possible.
|
||||||
@ -567,7 +528,7 @@ const (
|
|||||||
// The game's Update is called based on the specified TPS.
|
// The game's Update is called based on the specified TPS.
|
||||||
//
|
//
|
||||||
// Deprecated: as of v2.5. Use SetVsyncEnabled(false) instead.
|
// Deprecated: as of v2.5. Use SetVsyncEnabled(false) instead.
|
||||||
FPSModeVsyncOffMaximum FPSModeType = ui.FPSModeVsyncOffMaximum
|
FPSModeVsyncOffMaximum FPSModeType = FPSModeType(ui.FPSModeVsyncOffMaximum)
|
||||||
|
|
||||||
// FPSModeVsyncOffMinimum indicates that the game doesn't sync with vsync, and
|
// FPSModeVsyncOffMinimum indicates that the game doesn't sync with vsync, and
|
||||||
// the game is updated only when necessary.
|
// the game is updated only when necessary.
|
||||||
@ -580,7 +541,7 @@ const (
|
|||||||
//
|
//
|
||||||
// Deprecated: as of v2.5. Use SetScreenClearedEveryFrame(false) instead.
|
// Deprecated: as of v2.5. Use SetScreenClearedEveryFrame(false) instead.
|
||||||
// See examples/skipdraw for GPU optimization with SetScreenClearedEveryFrame(false).
|
// See examples/skipdraw for GPU optimization with SetScreenClearedEveryFrame(false).
|
||||||
FPSModeVsyncOffMinimum FPSModeType = ui.FPSModeVsyncOffMinimum
|
FPSModeVsyncOffMinimum FPSModeType = FPSModeType(ui.FPSModeVsyncOffMinimum)
|
||||||
)
|
)
|
||||||
|
|
||||||
// FPSMode returns the current FPS mode.
|
// FPSMode returns the current FPS mode.
|
||||||
@ -589,7 +550,7 @@ const (
|
|||||||
//
|
//
|
||||||
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
||||||
func FPSMode() FPSModeType {
|
func FPSMode() FPSModeType {
|
||||||
return ui.Get().FPSMode()
|
return FPSModeType(ui.Get().FPSMode())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFPSMode sets the FPS mode.
|
// SetFPSMode sets the FPS mode.
|
||||||
@ -599,7 +560,7 @@ func FPSMode() FPSModeType {
|
|||||||
//
|
//
|
||||||
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
// Deprecated: as of v2.5. Use SetVsyncEnabled instead.
|
||||||
func SetFPSMode(mode FPSModeType) {
|
func SetFPSMode(mode FPSModeType) {
|
||||||
ui.Get().SetFPSMode(mode)
|
ui.Get().SetFPSMode(ui.FPSModeType(mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleFrame schedules a next frame when the current FPS mode is FPSModeVsyncOffMinimum.
|
// ScheduleFrame schedules a next frame when the current FPS mode is FPSModeVsyncOffMinimum.
|
||||||
@ -736,6 +697,29 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
|
|||||||
if options.X11InstanceName == "" {
|
if options.X11InstanceName == "" {
|
||||||
options.X11InstanceName = defaultX11InstanceName
|
options.X11InstanceName = defaultX11InstanceName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ui.RunOptions.StrictContextRestoration is not used so far (#3098).
|
||||||
|
// This might be reused in the future.
|
||||||
|
// The original comment for StrictContextRestration is as follows:
|
||||||
|
//
|
||||||
|
// StrictContextRestration indicates whether the context lost should be restored strictly by Ebitengine or not.
|
||||||
|
//
|
||||||
|
// StrictContextRestration is available only on Android. Otherwise, StrictContextRestration is ignored.
|
||||||
|
// Thus, StrictContextRestration should be used with mobile.SetGameWithOptions, rather than RunGameWithOptions.
|
||||||
|
//
|
||||||
|
// In Android, Ebitengien uses `GLSurfaceView`'s `setPreserveEGLContextOnPause(true)`.
|
||||||
|
// This works in most cases, but it is still possible that the context is lost in some minor cases.
|
||||||
|
//
|
||||||
|
// When StrictContextRestration is true, Ebitengine tries to restore the context more strictly
|
||||||
|
// for such minor cases.
|
||||||
|
// However, this might cause a performance issue since Ebitengine tries to keep all the information
|
||||||
|
// to restore the context.
|
||||||
|
//
|
||||||
|
// When StrictContextRestration is false, Ebitengine does nothing special to restore the context and
|
||||||
|
// relies on the OS's behavior.
|
||||||
|
//
|
||||||
|
// The default (zero) value is false.
|
||||||
|
|
||||||
return &ui.RunOptions{
|
return &ui.RunOptions{
|
||||||
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
|
GraphicsLibrary: ui.GraphicsLibrary(options.GraphicsLibrary),
|
||||||
InitUnfocused: options.InitUnfocused,
|
InitUnfocused: options.InitUnfocused,
|
||||||
@ -746,7 +730,6 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
|
|||||||
ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace),
|
ColorSpace: graphicsdriver.ColorSpace(options.ColorSpace),
|
||||||
X11ClassName: options.X11ClassName,
|
X11ClassName: options.X11ClassName,
|
||||||
X11InstanceName: options.X11InstanceName,
|
X11InstanceName: options.X11InstanceName,
|
||||||
StrictContextRestoration: options.StrictContextRestration,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +20,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-text/typesetting/di"
|
"github.com/go-text/typesetting/di"
|
||||||
|
"github.com/go-text/typesetting/font"
|
||||||
glanguage "github.com/go-text/typesetting/language"
|
glanguage "github.com/go-text/typesetting/language"
|
||||||
"github.com/go-text/typesetting/opentype/api/font"
|
|
||||||
"github.com/go-text/typesetting/opentype/loader"
|
|
||||||
"github.com/go-text/typesetting/shaping"
|
"github.com/go-text/typesetting/shaping"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@ -89,7 +88,7 @@ func (g *GoTextFace) SetVariation(tag Tag, value float32) {
|
|||||||
g.variations = append(g.variations, font.Variation{})
|
g.variations = append(g.variations, font.Variation{})
|
||||||
copy(g.variations[idx+1:], g.variations[idx:])
|
copy(g.variations[idx+1:], g.variations[idx:])
|
||||||
g.variations[idx] = font.Variation{
|
g.variations[idx] = font.Variation{
|
||||||
Tag: loader.Tag(tag),
|
Tag: font.Tag(tag),
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
g.variationsString = ""
|
g.variationsString = ""
|
||||||
@ -136,7 +135,7 @@ func (g *GoTextFace) SetFeature(tag Tag, value uint32) {
|
|||||||
g.features = append(g.features, shaping.FontFeature{})
|
g.features = append(g.features, shaping.FontFeature{})
|
||||||
copy(g.features[idx+1:], g.features[idx:])
|
copy(g.features[idx+1:], g.features[idx:])
|
||||||
g.features[idx] = shaping.FontFeature{
|
g.features[idx] = shaping.FontFeature{
|
||||||
Tag: loader.Tag(tag),
|
Tag: font.Tag(tag),
|
||||||
Value: value,
|
Value: value,
|
||||||
}
|
}
|
||||||
g.featuresString = ""
|
g.featuresString = ""
|
||||||
@ -202,6 +201,21 @@ func (g *GoTextFace) Metrics() Metrics {
|
|||||||
m.VDescent = float64(-v.Descender) * scale
|
m.VDescent = float64(-v.Descender) * scale
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.XHeight = float64(g.Source.f.LineMetric(font.XHeight)) * scale
|
||||||
|
m.CapHeight = float64(g.Source.f.LineMetric(font.CapHeight)) * scale
|
||||||
|
|
||||||
|
// XHeight and CapHeight might not be correct for some old fonts (go-text/typesetting#169).
|
||||||
|
if m.XHeight <= 0 {
|
||||||
|
if _, gs := g.Source.shape("x", g); len(gs) > 0 {
|
||||||
|
m.XHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.CapHeight <= 0 {
|
||||||
|
if _, gs := g.Source.shape("H", g); len(gs) > 0 {
|
||||||
|
m.CapHeight = fixed26_6ToFloat64(-gs[0].bounds.Min.Y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,10 +20,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-text/typesetting/font"
|
"github.com/go-text/typesetting/font"
|
||||||
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
"github.com/go-text/typesetting/language"
|
"github.com/go-text/typesetting/language"
|
||||||
"github.com/go-text/typesetting/opentype/api"
|
|
||||||
ofont "github.com/go-text/typesetting/opentype/api/font"
|
|
||||||
"github.com/go-text/typesetting/opentype/loader"
|
|
||||||
"github.com/go-text/typesetting/shaping"
|
"github.com/go-text/typesetting/shaping"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
)
|
)
|
||||||
@ -42,7 +40,7 @@ type glyph struct {
|
|||||||
shapingGlyph *shaping.Glyph
|
shapingGlyph *shaping.Glyph
|
||||||
startIndex int
|
startIndex int
|
||||||
endIndex int
|
endIndex int
|
||||||
scaledSegments []api.Segment
|
scaledSegments []opentype.Segment
|
||||||
bounds fixed.Rectangle26_6
|
bounds fixed.Rectangle26_6
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +51,7 @@ type goTextOutputCacheValue struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type goTextGlyphImageCacheKey struct {
|
type goTextGlyphImageCacheKey struct {
|
||||||
gid api.GID
|
gid opentype.GID
|
||||||
xoffset fixed.Int26_6
|
xoffset fixed.Int26_6
|
||||||
yoffset fixed.Int26_6
|
yoffset fixed.Int26_6
|
||||||
variations string
|
variations string
|
||||||
@ -61,7 +59,7 @@ type goTextGlyphImageCacheKey struct {
|
|||||||
|
|
||||||
// GoTextFaceSource is a source of a GoTextFace. This can be shared by multiple GoTextFace objects.
|
// GoTextFaceSource is a source of a GoTextFace. This can be shared by multiple GoTextFace objects.
|
||||||
type GoTextFaceSource struct {
|
type GoTextFaceSource struct {
|
||||||
f font.Face
|
f *font.Face
|
||||||
metadata Metadata
|
metadata Metadata
|
||||||
|
|
||||||
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
outputCache map[goTextOutputCacheKey]*goTextOutputCacheValue
|
||||||
@ -99,18 +97,18 @@ func NewGoTextFaceSource(source io.Reader) (*GoTextFaceSource, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := loader.NewLoader(src)
|
l, err := opentype.NewLoader(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := ofont.NewFont(l)
|
f, err := font.NewFont(l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &GoTextFaceSource{
|
s := &GoTextFaceSource{
|
||||||
f: &ofont.Face{Font: f},
|
f: font.NewFace(f),
|
||||||
}
|
}
|
||||||
s.addr = s
|
s.addr = s
|
||||||
s.metadata = metadataFromLoader(l)
|
s.metadata = metadataFromLoader(l)
|
||||||
@ -125,19 +123,19 @@ func NewGoTextFaceSourcesFromCollection(source io.Reader) ([]*GoTextFaceSource,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ls, err := loader.NewLoaders(src)
|
ls, err := opentype.NewLoaders(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sources := make([]*GoTextFaceSource, len(ls))
|
sources := make([]*GoTextFaceSource, len(ls))
|
||||||
for i, l := range ls {
|
for i, l := range ls {
|
||||||
f, err := ofont.NewFont(l)
|
f, err := font.NewFont(l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s := &GoTextFaceSource{
|
s := &GoTextFaceSource{
|
||||||
f: &ofont.Face{Font: f},
|
f: &font.Face{Font: f},
|
||||||
}
|
}
|
||||||
s.addr = s
|
s.addr = s
|
||||||
s.metadata = metadataFromLoader(l)
|
s.metadata = metadataFromLoader(l)
|
||||||
@ -158,9 +156,12 @@ func (g *GoTextFaceSource) Metadata() Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnsafeInternal returns its font.Face.
|
// UnsafeInternal returns its font.Face.
|
||||||
|
// The return value type is any since github.com/go-text/typesettings's API is now unstable.
|
||||||
//
|
//
|
||||||
// This is unsafe since this might make internal cache states out of sync.
|
// UnsafeInternal is unsafe since this might make internal cache states out of sync.
|
||||||
func (g *GoTextFaceSource) UnsafeInternal() font.Face {
|
//
|
||||||
|
// UnsafeInternal might have breaking changes even in the same major version.
|
||||||
|
func (g *GoTextFaceSource) UnsafeInternal() any {
|
||||||
return g.f
|
return g.f
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,22 +219,22 @@ func (g *GoTextFaceSource) shape(text string, face *GoTextFace) ([]shaping.Outpu
|
|||||||
|
|
||||||
for _, gl := range out.Glyphs {
|
for _, gl := range out.Glyphs {
|
||||||
gl := gl
|
gl := gl
|
||||||
var segs []api.Segment
|
var segs []opentype.Segment
|
||||||
switch data := g.f.GlyphData(gl.GlyphID).(type) {
|
switch data := g.f.GlyphData(gl.GlyphID).(type) {
|
||||||
case api.GlyphOutline:
|
case font.GlyphOutline:
|
||||||
if out.Direction.IsSideways() {
|
if out.Direction.IsSideways() {
|
||||||
data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem()))
|
data.Sideways(fixed26_6ToFloat32(-gl.YOffset) / fixed26_6ToFloat32(out.Size) * float32(f.Upem()))
|
||||||
}
|
}
|
||||||
segs = data.Segments
|
segs = data.Segments
|
||||||
case api.GlyphSVG:
|
case font.GlyphSVG:
|
||||||
segs = data.Outline.Segments
|
segs = data.Outline.Segments
|
||||||
case api.GlyphBitmap:
|
case font.GlyphBitmap:
|
||||||
if data.Outline != nil {
|
if data.Outline != nil {
|
||||||
segs = data.Outline.Segments
|
segs = data.Outline.Segments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scaledSegs := make([]api.Segment, len(segs))
|
scaledSegs := make([]opentype.Segment, len(segs))
|
||||||
scale := float32(g.scale(fixed26_6ToFloat64(out.Size)))
|
scale := float32(g.scale(fixed26_6ToFloat64(out.Size)))
|
||||||
for i, seg := range segs {
|
for i, seg := range segs {
|
||||||
scaledSegs[i] = seg
|
scaledSegs[i] = seg
|
||||||
@ -291,9 +292,9 @@ func (g *GoTextFaceSource) getOrCreateGlyphImage(goTextFace *GoTextFace, key goT
|
|||||||
}
|
}
|
||||||
|
|
||||||
type singleFontmap struct {
|
type singleFontmap struct {
|
||||||
face font.Face
|
face *font.Face
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *singleFontmap) ResolveFace(r rune) font.Face {
|
func (s *singleFontmap) ResolveFace(r rune) *font.Face {
|
||||||
return s.face
|
return s.face
|
||||||
}
|
}
|
||||||
|
@ -19,15 +19,14 @@ import (
|
|||||||
"image/draw"
|
"image/draw"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
gvector "golang.org/x/image/vector"
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
|
|
||||||
"github.com/go-text/typesetting/opentype/api"
|
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
|
gvector "golang.org/x/image/vector"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||||
)
|
)
|
||||||
|
|
||||||
func segmentsToBounds(segs []api.Segment) fixed.Rectangle26_6 {
|
func segmentsToBounds(segs []opentype.Segment) fixed.Rectangle26_6 {
|
||||||
if len(segs) == 0 {
|
if len(segs) == 0 {
|
||||||
return fixed.Rectangle26_6{}
|
return fixed.Rectangle26_6{}
|
||||||
}
|
}
|
||||||
@ -40,9 +39,9 @@ func segmentsToBounds(segs []api.Segment) fixed.Rectangle26_6 {
|
|||||||
for _, seg := range segs {
|
for _, seg := range segs {
|
||||||
n := 1
|
n := 1
|
||||||
switch seg.Op {
|
switch seg.Op {
|
||||||
case api.SegmentOpQuadTo:
|
case opentype.SegmentOpQuadTo:
|
||||||
n = 2
|
n = 2
|
||||||
case api.SegmentOpCubeTo:
|
case opentype.SegmentOpCubeTo:
|
||||||
n = 3
|
n = 3
|
||||||
}
|
}
|
||||||
for i := 0; i < n; i++ {
|
for i := 0; i < n; i++ {
|
||||||
@ -75,7 +74,7 @@ func segmentsToBounds(segs []api.Segment) fixed.Rectangle26_6 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func segmentsToImage(a *glyphAtlas, segs []api.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *glyphImage {
|
func segmentsToImage(a *glyphAtlas, segs []opentype.Segment, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *glyphImage {
|
||||||
if len(segs) == 0 {
|
if len(segs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -97,16 +96,16 @@ func segmentsToImage(a *glyphAtlas, segs []api.Segment, subpixelOffset fixed.Poi
|
|||||||
rast.DrawOp = draw.Src
|
rast.DrawOp = draw.Src
|
||||||
for _, seg := range segs {
|
for _, seg := range segs {
|
||||||
switch seg.Op {
|
switch seg.Op {
|
||||||
case api.SegmentOpMoveTo:
|
case opentype.SegmentOpMoveTo:
|
||||||
rast.MoveTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
rast.MoveTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
||||||
case api.SegmentOpLineTo:
|
case opentype.SegmentOpLineTo:
|
||||||
rast.LineTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
rast.LineTo(seg.Args[0].X+biasX, seg.Args[0].Y+biasY)
|
||||||
case api.SegmentOpQuadTo:
|
case opentype.SegmentOpQuadTo:
|
||||||
rast.QuadTo(
|
rast.QuadTo(
|
||||||
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
||||||
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
||||||
)
|
)
|
||||||
case api.SegmentOpCubeTo:
|
case opentype.SegmentOpCubeTo:
|
||||||
rast.CubeTo(
|
rast.CubeTo(
|
||||||
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
seg.Args[0].X+biasX, seg.Args[0].Y+biasY,
|
||||||
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
seg.Args[1].X+biasX, seg.Args[1].Y+biasY,
|
||||||
@ -128,19 +127,19 @@ func segmentsToImage(a *glyphAtlas, segs []api.Segment, subpixelOffset fixed.Poi
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendVectorPathFromSegments(path *vector.Path, segs []api.Segment, x, y float32) {
|
func appendVectorPathFromSegments(path *vector.Path, segs []opentype.Segment, x, y float32) {
|
||||||
for _, seg := range segs {
|
for _, seg := range segs {
|
||||||
switch seg.Op {
|
switch seg.Op {
|
||||||
case api.SegmentOpMoveTo:
|
case opentype.SegmentOpMoveTo:
|
||||||
path.MoveTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
path.MoveTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
||||||
case api.SegmentOpLineTo:
|
case opentype.SegmentOpLineTo:
|
||||||
path.LineTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
path.LineTo(seg.Args[0].X+x, seg.Args[0].Y+y)
|
||||||
case api.SegmentOpQuadTo:
|
case opentype.SegmentOpQuadTo:
|
||||||
path.QuadTo(
|
path.QuadTo(
|
||||||
seg.Args[0].X+x, seg.Args[0].Y+y,
|
seg.Args[0].X+x, seg.Args[0].Y+y,
|
||||||
seg.Args[1].X+x, seg.Args[1].Y+y,
|
seg.Args[1].X+x, seg.Args[1].Y+y,
|
||||||
)
|
)
|
||||||
case api.SegmentOpCubeTo:
|
case opentype.SegmentOpCubeTo:
|
||||||
path.CubicTo(
|
path.CubicTo(
|
||||||
seg.Args[0].X+x, seg.Args[0].Y+y,
|
seg.Args[0].X+x, seg.Args[0].Y+y,
|
||||||
seg.Args[1].X+x, seg.Args[1].Y+y,
|
seg.Args[1].X+x, seg.Args[1].Y+y,
|
||||||
|
@ -44,6 +44,8 @@ type GoXFace struct {
|
|||||||
|
|
||||||
glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]
|
glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]
|
||||||
|
|
||||||
|
cachedMetrics Metrics
|
||||||
|
|
||||||
addr *GoXFace
|
addr *GoXFace
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,17 +70,35 @@ func (s *GoXFace) copyCheck() {
|
|||||||
func (s *GoXFace) Metrics() Metrics {
|
func (s *GoXFace) Metrics() Metrics {
|
||||||
s.copyCheck()
|
s.copyCheck()
|
||||||
|
|
||||||
m := s.f.Metrics()
|
if s.cachedMetrics != (Metrics{}) {
|
||||||
return Metrics{
|
return s.cachedMetrics
|
||||||
HLineGap: fixed26_6ToFloat64(m.Height - m.Ascent - m.Descent),
|
|
||||||
HAscent: fixed26_6ToFloat64(m.Ascent),
|
|
||||||
HDescent: fixed26_6ToFloat64(m.Descent),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fm := s.f.Metrics()
|
||||||
|
m := Metrics{
|
||||||
|
HLineGap: fixed26_6ToFloat64(fm.Height - fm.Ascent - fm.Descent),
|
||||||
|
HAscent: fixed26_6ToFloat64(fm.Ascent),
|
||||||
|
HDescent: fixed26_6ToFloat64(fm.Descent),
|
||||||
|
XHeight: fixed26_6ToFloat64(fm.XHeight),
|
||||||
|
CapHeight: fixed26_6ToFloat64(fm.CapHeight),
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is an issue that XHeight and CapHeight are negative for some old fonts (golang/go#69378).
|
||||||
|
if fm.XHeight < 0 {
|
||||||
|
m.XHeight *= -1
|
||||||
|
}
|
||||||
|
if fm.CapHeight < 0 {
|
||||||
|
m.CapHeight *= -1
|
||||||
|
}
|
||||||
|
s.cachedMetrics = m
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsafeInternal returns its internal font.Face.
|
// UnsafeInternal returns its internal font.Face.
|
||||||
//
|
//
|
||||||
// This is unsafe since this might make internal cache states out of sync.
|
// UnsafeInternal is unsafe since this might make internal cache states out of sync.
|
||||||
|
//
|
||||||
|
// UnsafeInternal might have breaking changes even in the same major version.
|
||||||
func (s *GoXFace) UnsafeInternal() font.Face {
|
func (s *GoXFace) UnsafeInternal() font.Face {
|
||||||
s.copyCheck()
|
s.copyCheck()
|
||||||
return s.f.f
|
return s.f.f
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
package text
|
package text
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-text/typesetting/opentype/api/metadata"
|
"github.com/go-text/typesetting/font"
|
||||||
"github.com/go-text/typesetting/opentype/loader"
|
"github.com/go-text/typesetting/font/opentype"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Metadata represents a font face's metadata.
|
// Metadata represents a font face's metadata.
|
||||||
@ -27,47 +27,47 @@ type Metadata struct {
|
|||||||
Stretch Stretch
|
Stretch Stretch
|
||||||
}
|
}
|
||||||
|
|
||||||
func metadataFromLoader(l *loader.Loader) Metadata {
|
func metadataFromLoader(l *opentype.Loader) Metadata {
|
||||||
f, a, _ := metadata.Describe(l, nil)
|
d, _ := font.Describe(l, nil)
|
||||||
return Metadata{
|
return Metadata{
|
||||||
Family: f,
|
Family: d.Family,
|
||||||
Style: Style(a.Style),
|
Style: Style(d.Aspect.Style),
|
||||||
Weight: Weight(a.Weight),
|
Weight: Weight(d.Aspect.Weight),
|
||||||
Stretch: Stretch(a.Stretch),
|
Stretch: Stretch(d.Aspect.Stretch),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Style uint8
|
type Style uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StyleNormal Style = Style(metadata.StyleNormal)
|
StyleNormal Style = Style(font.StyleNormal)
|
||||||
StyleItalic Style = Style(metadata.StyleItalic)
|
StyleItalic Style = Style(font.StyleItalic)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Weight float32
|
type Weight float32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
WeightThin Weight = Weight(metadata.WeightThin)
|
WeightThin Weight = Weight(font.WeightThin)
|
||||||
WeightExtraLight Weight = Weight(metadata.WeightExtraLight)
|
WeightExtraLight Weight = Weight(font.WeightExtraLight)
|
||||||
WeightLight Weight = Weight(metadata.WeightLight)
|
WeightLight Weight = Weight(font.WeightLight)
|
||||||
WeightNormal Weight = Weight(metadata.WeightNormal)
|
WeightNormal Weight = Weight(font.WeightNormal)
|
||||||
WeightMedium Weight = Weight(metadata.WeightMedium)
|
WeightMedium Weight = Weight(font.WeightMedium)
|
||||||
WeightSemibold Weight = Weight(metadata.WeightSemibold)
|
WeightSemibold Weight = Weight(font.WeightSemibold)
|
||||||
WeightBold Weight = Weight(metadata.WeightBold)
|
WeightBold Weight = Weight(font.WeightBold)
|
||||||
WeightExtraBold Weight = Weight(metadata.WeightExtraBold)
|
WeightExtraBold Weight = Weight(font.WeightExtraBold)
|
||||||
WeightBlack Weight = Weight(metadata.WeightBlack)
|
WeightBlack Weight = Weight(font.WeightBlack)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Stretch float32
|
type Stretch float32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StretchUltraCondensed Stretch = Stretch(metadata.StretchUltraCondensed)
|
StretchUltraCondensed Stretch = Stretch(font.StretchUltraCondensed)
|
||||||
StretchExtraCondensed Stretch = Stretch(metadata.StretchExtraCondensed)
|
StretchExtraCondensed Stretch = Stretch(font.StretchExtraCondensed)
|
||||||
StretchCondensed Stretch = Stretch(metadata.StretchCondensed)
|
StretchCondensed Stretch = Stretch(font.StretchCondensed)
|
||||||
StretchSemiCondensed Stretch = Stretch(metadata.StretchSemiCondensed)
|
StretchSemiCondensed Stretch = Stretch(font.StretchSemiCondensed)
|
||||||
StretchNormal Stretch = Stretch(metadata.StretchNormal)
|
StretchNormal Stretch = Stretch(font.StretchNormal)
|
||||||
StretchSemiExpanded Stretch = Stretch(metadata.StretchSemiExpanded)
|
StretchSemiExpanded Stretch = Stretch(font.StretchSemiExpanded)
|
||||||
StretchExpanded Stretch = Stretch(metadata.StretchExpanded)
|
StretchExpanded Stretch = Stretch(font.StretchExpanded)
|
||||||
StretchExtraExpanded Stretch = Stretch(metadata.StretchExtraExpanded)
|
StretchExtraExpanded Stretch = Stretch(font.StretchExtraExpanded)
|
||||||
StretchUltraExpanded Stretch = Stretch(metadata.StretchUltraExpanded)
|
StretchUltraExpanded Stretch = Stretch(font.StretchUltraExpanded)
|
||||||
)
|
)
|
||||||
|
@ -75,6 +75,12 @@ func (m *MultiFace) Metrics() Metrics {
|
|||||||
if mt1.VDescent > mt.VDescent {
|
if mt1.VDescent > mt.VDescent {
|
||||||
mt.VDescent = mt1.VDescent
|
mt.VDescent = mt1.VDescent
|
||||||
}
|
}
|
||||||
|
if mt1.XHeight > mt.XHeight {
|
||||||
|
mt.XHeight = mt1.XHeight
|
||||||
|
}
|
||||||
|
if mt1.CapHeight > mt.CapHeight {
|
||||||
|
mt.CapHeight = mt1.CapHeight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return mt
|
return mt
|
||||||
}
|
}
|
||||||
|
310
text/v2/testdata/LICENSE.md
vendored
Normal file
310
text/v2/testdata/LICENSE.md
vendored
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
# Licenses
|
||||||
|
|
||||||
|
## `MPLUS1p-Regular.ttf`
|
||||||
|
|
||||||
|
https://fonts.google.com/specimen/M+PLUS+1p
|
||||||
|
|
||||||
|
```
|
||||||
|
Copyright 2016 The M+ Project Authors.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
```
|
||||||
|
|
||||||
|
## `Roboto-Regular.ttf`
|
||||||
|
|
||||||
|
https://fonts.google.com/specimen/Roboto
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
|
```
|
BIN
text/v2/testdata/MPLUS1p-Regular.ttf
vendored
Normal file
BIN
text/v2/testdata/MPLUS1p-Regular.ttf
vendored
Normal file
Binary file not shown.
BIN
text/v2/testdata/Roboto-Regular.ttf
vendored
Normal file
BIN
text/v2/testdata/Roboto-Regular.ttf
vendored
Normal file
Binary file not shown.
@ -69,6 +69,12 @@ type Metrics struct {
|
|||||||
// VDescent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
// VDescent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
||||||
// If the face is GoXFace or the font doesn't support a vertical direction, VDescent is 0.
|
// If the face is GoXFace or the font doesn't support a vertical direction, VDescent is 0.
|
||||||
VDescent float64
|
VDescent float64
|
||||||
|
|
||||||
|
// XHeight is the distance in pixels from the baseline to the top of the lower case letters.
|
||||||
|
XHeight float64
|
||||||
|
|
||||||
|
// CapHeight is the distance in pixels from the baseline to the top of the capital letters.
|
||||||
|
CapHeight float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixed26_6ToFloat32(x fixed.Int26_6) float32 {
|
func fixed26_6ToFloat32(x fixed.Int26_6) float32 {
|
||||||
|
@ -18,6 +18,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -25,6 +28,7 @@ import (
|
|||||||
"github.com/hajimehoshi/bitmapfont/v3"
|
"github.com/hajimehoshi/bitmapfont/v3"
|
||||||
"golang.org/x/image/font"
|
"golang.org/x/image/font"
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
|
"golang.org/x/image/font/opentype"
|
||||||
"golang.org/x/image/math/fixed"
|
"golang.org/x/image/math/fixed"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
@ -374,6 +378,78 @@ func TestDrawOptionsNotModified(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGoXFaceMetrics(t *testing.T) {
|
||||||
|
const size = 100
|
||||||
|
|
||||||
|
fontFiles := []string{
|
||||||
|
// MPLUS1p-Regular.ttf is an old version of M+ 1p font, and this doesn't have metadata.
|
||||||
|
"MPLUS1p-Regular.ttf",
|
||||||
|
"Roboto-Regular.ttf",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fontFile := range fontFiles {
|
||||||
|
fontFile := fontFile
|
||||||
|
t.Run(fontFile, func(t *testing.T) {
|
||||||
|
fontdata, err := os.ReadFile(filepath.Join("testdata", fontFile))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sfntFont, err := opentype.Parse(fontdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
opentypeFace, err := opentype.NewFace(sfntFont, &opentype.FaceOptions{
|
||||||
|
Size: size,
|
||||||
|
DPI: 72,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
goXFace := text.NewGoXFace(opentypeFace)
|
||||||
|
goXMetrics := goXFace.Metrics()
|
||||||
|
if goXMetrics.XHeight <= 0 {
|
||||||
|
t.Errorf("GoXFace's XHeight must be positive but not: %f", goXMetrics.XHeight)
|
||||||
|
}
|
||||||
|
if goXMetrics.CapHeight <= 0 {
|
||||||
|
t.Errorf("GoXFace's CapHeight must be positive but not: %f", goXMetrics.CapHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
goTextFaceSource, err := text.NewGoTextFaceSource(bytes.NewBuffer(fontdata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
goTextFace := &text.GoTextFace{
|
||||||
|
Source: goTextFaceSource,
|
||||||
|
Size: size,
|
||||||
|
}
|
||||||
|
goTextMetrics := goTextFace.Metrics()
|
||||||
|
if goTextMetrics.XHeight <= 0 {
|
||||||
|
t.Errorf("GoTextFace's XHeight must be positive but not: %f", goTextMetrics.XHeight)
|
||||||
|
}
|
||||||
|
if goTextMetrics.CapHeight <= 0 {
|
||||||
|
t.Errorf("GoTextFace's CapHeight must be positive but not: %f", goTextMetrics.CapHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
if math.Abs(goXMetrics.XHeight-goTextMetrics.XHeight) >= 0.1 {
|
||||||
|
t.Errorf("XHeight values don't match: %f (GoXFace) vs %f (GoTextFace)", goXMetrics.XHeight, goTextMetrics.XHeight)
|
||||||
|
}
|
||||||
|
if math.Abs(goXMetrics.CapHeight-goTextMetrics.CapHeight) >= 0.1 {
|
||||||
|
t.Errorf("CapHeight values don't match: %f (GoXFace) vs %f (GoTextFace)", goXMetrics.CapHeight, goTextMetrics.CapHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a MultiFace should have the same metrics.
|
||||||
|
multiFace, err := text.NewMultiFace(goTextFace)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := multiFace.Metrics(); got != goTextMetrics {
|
||||||
|
t.Errorf("got: %v, want: %v", got, goTextMetrics)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkDrawText(b *testing.B) {
|
func BenchmarkDrawText(b *testing.B) {
|
||||||
var txt string
|
var txt string
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
|
12
window.go
12
window.go
@ -25,21 +25,21 @@ import (
|
|||||||
//
|
//
|
||||||
// Regardless of the resizing mode, an Ebitengine application can still change the window size or make
|
// Regardless of the resizing mode, an Ebitengine application can still change the window size or make
|
||||||
// the window fullscreen by calling Ebitengine functions.
|
// the window fullscreen by calling Ebitengine functions.
|
||||||
type WindowResizingModeType = ui.WindowResizingMode
|
type WindowResizingModeType int
|
||||||
|
|
||||||
// WindowResizingModeTypes
|
// WindowResizingModeTypes
|
||||||
const (
|
const (
|
||||||
// WindowResizingModeDisabled indicates the mode to disallow resizing the window by a user.
|
// WindowResizingModeDisabled indicates the mode to disallow resizing the window by a user.
|
||||||
WindowResizingModeDisabled WindowResizingModeType = ui.WindowResizingModeDisabled
|
WindowResizingModeDisabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeDisabled)
|
||||||
|
|
||||||
// WindowResizingModeOnlyFullscreenEnabled indicates the mode to disallow resizing the window,
|
// WindowResizingModeOnlyFullscreenEnabled indicates the mode to disallow resizing the window,
|
||||||
// but allow to make the window fullscreen by a user.
|
// but allow to make the window fullscreen by a user.
|
||||||
// This works only on macOS so far.
|
// This works only on macOS so far.
|
||||||
// On the other platforms, this is the same as WindowResizingModeDisabled.
|
// On the other platforms, this is the same as WindowResizingModeDisabled.
|
||||||
WindowResizingModeOnlyFullscreenEnabled WindowResizingModeType = ui.WindowResizingModeOnlyFullscreenEnabled
|
WindowResizingModeOnlyFullscreenEnabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeOnlyFullscreenEnabled)
|
||||||
|
|
||||||
// WindowResizingModeEnabled indicates the mode to allow resizing the window by a user.
|
// WindowResizingModeEnabled indicates the mode to allow resizing the window by a user.
|
||||||
WindowResizingModeEnabled WindowResizingModeType = ui.WindowResizingModeEnabled
|
WindowResizingModeEnabled WindowResizingModeType = WindowResizingModeType(ui.WindowResizingModeEnabled)
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsWindowDecorated reports whether the window is decorated.
|
// IsWindowDecorated reports whether the window is decorated.
|
||||||
@ -67,14 +67,14 @@ func SetWindowDecorated(decorated bool) {
|
|||||||
//
|
//
|
||||||
// WindowResizingMode is concurrent-safe.
|
// WindowResizingMode is concurrent-safe.
|
||||||
func WindowResizingMode() WindowResizingModeType {
|
func WindowResizingMode() WindowResizingModeType {
|
||||||
return ui.Get().Window().ResizingMode()
|
return WindowResizingModeType(ui.Get().Window().ResizingMode())
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetWindowResizingMode sets the mode in which a user resizes the window.
|
// SetWindowResizingMode sets the mode in which a user resizes the window.
|
||||||
//
|
//
|
||||||
// SetWindowResizingMode is concurrent-safe.
|
// SetWindowResizingMode is concurrent-safe.
|
||||||
func SetWindowResizingMode(mode WindowResizingModeType) {
|
func SetWindowResizingMode(mode WindowResizingModeType) {
|
||||||
ui.Get().Window().SetResizingMode(mode)
|
ui.Get().Window().SetResizingMode(ui.WindowResizingMode(mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWindowResizable reports whether the window is resizable by the user's dragging on desktops.
|
// IsWindowResizable reports whether the window is resizable by the user's dragging on desktops.
|
||||||
|
Loading…
Reference in New Issue
Block a user