mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
32 Commits
0ddc018190
...
c73ee2630a
Author | SHA1 | Date | |
---|---|---|---|
|
c73ee2630a | ||
|
b7dd45c0e4 | ||
|
4b1c0526a7 | ||
|
cd90f083bc | ||
|
d15b12b4e5 | ||
|
f79f6dc55f | ||
|
6bbfec1869 | ||
|
4a212181e7 | ||
|
9cd525a04e | ||
|
9cc017412f | ||
|
9faa3f4601 | ||
|
696938987d | ||
|
209dc50f72 | ||
|
047858aa59 | ||
|
6cdabf09d1 | ||
|
bb6430d3ba | ||
|
7389f9ddb2 | ||
|
4c7ed56077 | ||
|
63e97c7064 | ||
|
c9a973c6c1 | ||
|
9a7dcb1077 | ||
|
927e025982 | ||
|
dc05f2014f | ||
|
3eaa03e193 | ||
|
34cdb20276 | ||
|
c0d9954b3e | ||
|
3e4c47eb70 | ||
|
4d72f97e45 | ||
|
0fa39182cb | ||
|
0a20670f3f | ||
|
c5ebf8670b | ||
|
200c6569c3 |
@ -60,15 +60,9 @@ const (
|
|||||||
type Context struct {
|
type Context struct {
|
||||||
playerFactory *playerFactory
|
playerFactory *playerFactory
|
||||||
|
|
||||||
// inited represents whether the audio device is initialized and available or not.
|
|
||||||
// On Android, audio loop cannot be started unless JVM is accessible. After updating one frame, JVM should exist.
|
|
||||||
inited chan struct{}
|
|
||||||
initedOnce sync.Once
|
|
||||||
|
|
||||||
sampleRate int
|
sampleRate int
|
||||||
err error
|
err error
|
||||||
ready bool
|
ready bool
|
||||||
readyOnce sync.Once
|
|
||||||
|
|
||||||
playingPlayers map[*playerImpl]struct{}
|
playingPlayers map[*playerImpl]struct{}
|
||||||
|
|
||||||
@ -100,7 +94,6 @@ func NewContext(sampleRate int) *Context {
|
|||||||
sampleRate: sampleRate,
|
sampleRate: sampleRate,
|
||||||
playerFactory: newPlayerFactory(sampleRate),
|
playerFactory: newPlayerFactory(sampleRate),
|
||||||
playingPlayers: map[*playerImpl]struct{}{},
|
playingPlayers: map[*playerImpl]struct{}{},
|
||||||
inited: make(chan struct{}),
|
|
||||||
semaphore: make(chan struct{}, 1),
|
semaphore: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
theContext = c
|
theContext = c
|
||||||
@ -128,10 +121,6 @@ func NewContext(sampleRate int) *Context {
|
|||||||
})
|
})
|
||||||
|
|
||||||
h.AppendHookOnBeforeUpdate(func() error {
|
h.AppendHookOnBeforeUpdate(func() error {
|
||||||
c.initedOnce.Do(func() {
|
|
||||||
close(c.inited)
|
|
||||||
})
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
theContextLock.Lock()
|
theContextLock.Lock()
|
||||||
if theContext != nil {
|
if theContext != nil {
|
||||||
@ -142,6 +131,19 @@ func NewContext(sampleRate int) *Context {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the context here in the case when there is no player and
|
||||||
|
// the program waits for IsReady() to be true (#969, #970, #2715).
|
||||||
|
ready, err := c.playerFactory.initContextIfNeeded()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ready != nil {
|
||||||
|
go func() {
|
||||||
|
<-ready
|
||||||
|
c.setReady()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.updatePlayers(); err != nil {
|
if err := c.updatePlayers(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -287,28 +289,7 @@ func (c *Context) updatePlayers() error {
|
|||||||
func (c *Context) IsReady() bool {
|
func (c *Context) IsReady() bool {
|
||||||
c.m.Lock()
|
c.m.Lock()
|
||||||
defer c.m.Unlock()
|
defer c.m.Unlock()
|
||||||
|
return c.ready
|
||||||
if c.ready {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(c.playingPlayers) != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
c.readyOnce.Do(func() {
|
|
||||||
// Create another goroutine since (*Player).Play can lock the context's mutex.
|
|
||||||
// TODO: Is this needed for reader players?
|
|
||||||
go func() {
|
|
||||||
// The audio context is never ready unless there is a player. This is
|
|
||||||
// problematic when a user tries to play audio after the context is ready.
|
|
||||||
// Play a dummy player to avoid the blocking (#969).
|
|
||||||
// Use a long enough buffer so that writing doesn't finish immediately (#970).
|
|
||||||
p := NewPlayerFromBytes(c, make([]byte, 16384))
|
|
||||||
p.Play()
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SampleRate returns the sample rate.
|
// SampleRate returns the sample rate.
|
||||||
@ -316,18 +297,6 @@ func (c *Context) SampleRate() int {
|
|||||||
return c.sampleRate
|
return c.sampleRate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) acquireSemaphore() {
|
|
||||||
c.semaphore <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) releaseSemaphore() {
|
|
||||||
<-c.semaphore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) waitUntilInited() {
|
|
||||||
<-c.inited
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player is an audio player which has one stream.
|
// Player is an audio player which has one stream.
|
||||||
//
|
//
|
||||||
// Even when all references to a Player object is gone,
|
// Even when all references to a Player object is gone,
|
||||||
|
@ -30,20 +30,12 @@ func newContext(sampleRate int) (context, chan struct{}, error) {
|
|||||||
return &contextProxy{ctx}, ready, err
|
return &contextProxy{ctx}, ready, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// otoContext is an interface for *oto.Context.
|
|
||||||
type otoContext interface {
|
|
||||||
NewPlayer(io.Reader) *oto.Player
|
|
||||||
Suspend() error
|
|
||||||
Resume() error
|
|
||||||
Err() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// contextProxy is a proxy between otoContext and context.
|
// contextProxy is a proxy between otoContext and context.
|
||||||
type contextProxy struct {
|
type contextProxy struct {
|
||||||
otoContext
|
*oto.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPlayer implements context.
|
// NewPlayer implements context.
|
||||||
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
func (c *contextProxy) NewPlayer(r io.Reader) player {
|
||||||
return c.otoContext.NewPlayer(r)
|
return c.Context.NewPlayer(r)
|
||||||
}
|
}
|
||||||
|
@ -356,6 +356,10 @@ func (p *playerImpl) updatePosition() {
|
|||||||
p.adjustedPosition = 0
|
p.adjustedPosition = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !p.context.IsReady() {
|
||||||
|
p.adjustedPosition = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
samples := (p.stream.position() - int64(p.player.BufferedSize())) / bytesPerSampleInt16
|
samples := (p.stream.position() - int64(p.player.BufferedSize())) / bytesPerSampleInt16
|
||||||
|
|
||||||
|
@ -244,12 +244,6 @@ public class EbitenView extends ViewGroup implements InputManager.InputDeviceLis
|
|||||||
int axisMask = getAxisMask(inputDevice);
|
int axisMask = getAxisMask(inputDevice);
|
||||||
|
|
||||||
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask);
|
Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask);
|
||||||
|
|
||||||
// Initialize the trigger axes values explicitly, or the initial button values would be 0.5 instead of 0.
|
|
||||||
if (gamepad.axes.size() >= 6) {
|
|
||||||
Ebitenmobileview.onGamepadAxisChanged(deviceId, 4, -1);
|
|
||||||
Ebitenmobileview.onGamepadAxisChanged(deviceId, 5, -1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The implementation is copied from SDL:
|
// The implementation is copied from SDL:
|
||||||
|
@ -347,7 +347,8 @@ func (p *Player) draw(screen *ebiten.Image) {
|
|||||||
// Compose the current time text.
|
// Compose the current time text.
|
||||||
m := (c / time.Minute) % 100
|
m := (c / time.Minute) % 100
|
||||||
s := (c / time.Second) % 60
|
s := (c / time.Second) % 60
|
||||||
currentTimeStr := fmt.Sprintf("%02d:%02d", m, s)
|
ms := (c / time.Millisecond) % 1000
|
||||||
|
currentTimeStr := fmt.Sprintf("%02d:%02d.%03d", m, s, ms)
|
||||||
|
|
||||||
// Draw buttons
|
// Draw buttons
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
@ -35,7 +35,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inconsolataFace = text.NewStdFace(inconsolata.Bold8x16)
|
inconsolataFace = text.NewGoXFace(inconsolata.Bold8x16)
|
||||||
)
|
)
|
||||||
|
|
||||||
// mode is a blend mode with description.
|
// mode is a blend mode with description.
|
||||||
|
@ -35,7 +35,7 @@ const (
|
|||||||
screenHeight = 240
|
screenHeight = 240
|
||||||
)
|
)
|
||||||
|
|
||||||
var fontFace = text.NewStdFace(bitmapfont.Face)
|
var fontFace = text.NewGoXFace(bitmapfont.Face)
|
||||||
|
|
||||||
var keyboardImage *ebiten.Image
|
var keyboardImage *ebiten.Image
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fontFace = text.NewStdFace(bitmapfont.FaceEA)
|
var fontFace = text.NewGoXFace(bitmapfont.FaceEA)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
screenWidth = 640
|
screenWidth = 640
|
||||||
@ -40,16 +40,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TextField struct {
|
type TextField struct {
|
||||||
bounds image.Rectangle
|
bounds image.Rectangle
|
||||||
multilines bool
|
multilines bool
|
||||||
text string
|
field textinput.Field
|
||||||
selectionStart int
|
|
||||||
selectionEnd int
|
|
||||||
focused bool
|
|
||||||
|
|
||||||
ch chan textinput.State
|
|
||||||
end func()
|
|
||||||
state textinput.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTextField(bounds image.Rectangle, multilines bool) *TextField {
|
func NewTextField(bounds image.Rectangle, multilines bool) *TextField {
|
||||||
@ -64,13 +57,11 @@ func (t *TextField) Contains(x, y int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TextField) SetSelectionStartByCursorPosition(x, y int) bool {
|
func (t *TextField) SetSelectionStartByCursorPosition(x, y int) bool {
|
||||||
t.cleanUp()
|
|
||||||
idx, ok := t.textIndexByCursorPosition(x, y)
|
idx, ok := t.textIndexByCursorPosition(x, y)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
t.selectionStart = idx
|
t.field.SetSelection(idx, idx)
|
||||||
t.selectionEnd = idx
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,20 +86,21 @@ func (t *TextField) textIndexByCursorPosition(x, y int) (int, bool) {
|
|||||||
var nlCount int
|
var nlCount int
|
||||||
var lineStart int
|
var lineStart int
|
||||||
var prevAdvance float64
|
var prevAdvance float64
|
||||||
for i, r := range t.text {
|
txt := t.field.Text()
|
||||||
|
for i, r := range txt {
|
||||||
var x0, x1 int
|
var x0, x1 int
|
||||||
currentAdvance := text.Advance(t.text[lineStart:i], fontFace)
|
currentAdvance := text.Advance(txt[lineStart:i], fontFace)
|
||||||
if lineStart < i {
|
if lineStart < i {
|
||||||
x0 = int((prevAdvance + currentAdvance) / 2)
|
x0 = int((prevAdvance + currentAdvance) / 2)
|
||||||
}
|
}
|
||||||
if r == '\n' {
|
if r == '\n' {
|
||||||
x1 = int(math.MaxInt32)
|
x1 = int(math.MaxInt32)
|
||||||
} else if i < len(t.text) {
|
} else if i < len(txt) {
|
||||||
nextI := i + 1
|
nextI := i + 1
|
||||||
for !utf8.ValidString(t.text[i:nextI]) {
|
for !utf8.ValidString(txt[i:nextI]) {
|
||||||
nextI++
|
nextI++
|
||||||
}
|
}
|
||||||
nextAdvance := text.Advance(t.text[lineStart:nextI], fontFace)
|
nextAdvance := text.Advance(txt[lineStart:nextI], fontFace)
|
||||||
x1 = int((currentAdvance + nextAdvance) / 2)
|
x1 = int((currentAdvance + nextAdvance) / 2)
|
||||||
} else {
|
} else {
|
||||||
x1 = int(currentAdvance)
|
x1 = int(currentAdvance)
|
||||||
@ -125,155 +117,109 @@ func (t *TextField) textIndexByCursorPosition(x, y int) (int, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(t.text), true
|
return len(txt), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TextField) Focus() {
|
func (t *TextField) Focus() {
|
||||||
t.focused = true
|
t.field.Focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TextField) Blur() {
|
func (t *TextField) Blur() {
|
||||||
t.focused = false
|
t.field.Blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TextField) cleanUp() {
|
func (t *TextField) Update() error {
|
||||||
if t.ch != nil {
|
if !t.field.IsFocused() {
|
||||||
select {
|
return nil
|
||||||
case state, ok := <-t.ch:
|
|
||||||
if ok && state.Committed {
|
|
||||||
t.text = t.text[:t.selectionStart] + state.Text + t.text[t.selectionEnd:]
|
|
||||||
t.selectionStart += len(state.Text)
|
|
||||||
t.selectionEnd = t.selectionStart
|
|
||||||
t.state = textinput.State{}
|
|
||||||
}
|
|
||||||
t.state = state
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.end != nil {
|
|
||||||
t.end()
|
|
||||||
t.ch = nil
|
|
||||||
t.end = nil
|
|
||||||
t.state = textinput.State{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TextField) Update() {
|
|
||||||
if !t.focused {
|
|
||||||
// If the text field still has a session, read the last state and process it just in case.
|
|
||||||
t.cleanUp()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var processed bool
|
x, y := t.bounds.Min.X, t.bounds.Min.Y
|
||||||
|
cx, cy := t.cursorPos()
|
||||||
// Text inputting can happen multiple times in one tick (1/60[s] by default).
|
px, py := textFieldPadding()
|
||||||
// Handle all of them.
|
x += cx + px
|
||||||
for {
|
y += cy + py + int(fontFace.Metrics().HAscent)
|
||||||
if t.ch == nil {
|
handled, err := t.field.HandleInput(x, y)
|
||||||
x, y := t.bounds.Min.X, t.bounds.Min.Y
|
if err != nil {
|
||||||
cx, cy := t.cursorPos()
|
return err
|
||||||
px, py := textFieldPadding()
|
|
||||||
x += cx + px
|
|
||||||
y += cy + py + int(fontFace.Metrics().HAscent)
|
|
||||||
t.ch, t.end = textinput.Start(x, y)
|
|
||||||
// Start returns nil for non-supported envrionments.
|
|
||||||
if t.ch == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readchar:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case state, ok := <-t.ch:
|
|
||||||
processed = true
|
|
||||||
if !ok {
|
|
||||||
t.ch = nil
|
|
||||||
t.end = nil
|
|
||||||
t.state = textinput.State{}
|
|
||||||
break readchar
|
|
||||||
}
|
|
||||||
if state.Committed {
|
|
||||||
t.text = t.text[:t.selectionStart] + state.Text + t.text[t.selectionEnd:]
|
|
||||||
t.selectionStart += len(state.Text)
|
|
||||||
t.selectionEnd = t.selectionStart
|
|
||||||
t.state = textinput.State{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.state = state
|
|
||||||
default:
|
|
||||||
break readchar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.ch == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
if handled {
|
||||||
if processed {
|
return nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case inpututil.IsKeyJustPressed(ebiten.KeyEnter):
|
case inpututil.IsKeyJustPressed(ebiten.KeyEnter):
|
||||||
if t.multilines {
|
if t.multilines {
|
||||||
t.text = t.text[:t.selectionStart] + "\n" + t.text[t.selectionEnd:]
|
text := t.field.Text()
|
||||||
t.selectionStart += 1
|
selectionStart, selectionEnd := t.field.Selection()
|
||||||
t.selectionEnd = t.selectionStart
|
text = text[:selectionStart] + "\n" + text[selectionEnd:]
|
||||||
|
selectionStart += len("\n")
|
||||||
|
selectionEnd = selectionStart
|
||||||
|
t.field.SetTextAndSelection(text, selectionStart, selectionEnd)
|
||||||
}
|
}
|
||||||
case inpututil.IsKeyJustPressed(ebiten.KeyBackspace):
|
case inpututil.IsKeyJustPressed(ebiten.KeyBackspace):
|
||||||
if t.selectionStart > 0 {
|
text := t.field.Text()
|
||||||
|
selectionStart, selectionEnd := t.field.Selection()
|
||||||
|
if selectionStart != selectionEnd {
|
||||||
|
text = text[:selectionStart] + text[selectionEnd:]
|
||||||
|
} else if selectionStart > 0 {
|
||||||
// TODO: Remove a grapheme instead of a code point.
|
// TODO: Remove a grapheme instead of a code point.
|
||||||
_, l := utf8.DecodeLastRuneInString(t.text[:t.selectionStart])
|
_, l := utf8.DecodeLastRuneInString(text[:selectionStart])
|
||||||
t.text = t.text[:t.selectionStart-l] + t.text[t.selectionEnd:]
|
text = text[:selectionStart-l] + text[selectionEnd:]
|
||||||
t.selectionStart -= l
|
selectionStart -= l
|
||||||
}
|
}
|
||||||
t.selectionEnd = t.selectionStart
|
selectionEnd = selectionStart
|
||||||
|
t.field.SetTextAndSelection(text, selectionStart, selectionEnd)
|
||||||
case inpututil.IsKeyJustPressed(ebiten.KeyLeft):
|
case inpututil.IsKeyJustPressed(ebiten.KeyLeft):
|
||||||
if t.selectionStart > 0 {
|
text := t.field.Text()
|
||||||
|
selectionStart, _ := t.field.Selection()
|
||||||
|
if selectionStart > 0 {
|
||||||
// TODO: Remove a grapheme instead of a code point.
|
// TODO: Remove a grapheme instead of a code point.
|
||||||
_, l := utf8.DecodeLastRuneInString(t.text[:t.selectionStart])
|
_, l := utf8.DecodeLastRuneInString(text[:selectionStart])
|
||||||
t.selectionStart -= l
|
selectionStart -= l
|
||||||
}
|
}
|
||||||
t.selectionEnd = t.selectionStart
|
t.field.SetTextAndSelection(text, selectionStart, selectionStart)
|
||||||
case inpututil.IsKeyJustPressed(ebiten.KeyRight):
|
case inpututil.IsKeyJustPressed(ebiten.KeyRight):
|
||||||
if t.selectionEnd < len(t.text) {
|
text := t.field.Text()
|
||||||
|
_, selectionEnd := t.field.Selection()
|
||||||
|
if selectionEnd < len(text) {
|
||||||
// TODO: Remove a grapheme instead of a code point.
|
// TODO: Remove a grapheme instead of a code point.
|
||||||
_, l := utf8.DecodeRuneInString(t.text[t.selectionEnd:])
|
_, l := utf8.DecodeRuneInString(text[selectionEnd:])
|
||||||
t.selectionEnd += l
|
selectionEnd += l
|
||||||
}
|
}
|
||||||
t.selectionStart = t.selectionEnd
|
t.field.SetTextAndSelection(text, selectionEnd, selectionEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !t.multilines {
|
if !t.multilines {
|
||||||
orig := t.text
|
orig := t.field.Text()
|
||||||
new := strings.ReplaceAll(orig, "\n", "")
|
new := strings.ReplaceAll(orig, "\n", "")
|
||||||
if new != orig {
|
if new != orig {
|
||||||
t.selectionStart -= strings.Count(orig[:t.selectionStart], "\n")
|
selectionStart, selectionEnd := t.field.Selection()
|
||||||
t.selectionEnd -= strings.Count(orig[:t.selectionEnd], "\n")
|
selectionStart -= strings.Count(orig[:selectionStart], "\n")
|
||||||
|
selectionEnd -= strings.Count(orig[:selectionEnd], "\n")
|
||||||
|
t.field.SetSelection(selectionStart, selectionEnd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TextField) cursorPos() (int, int) {
|
func (t *TextField) cursorPos() (int, int) {
|
||||||
var nlCount int
|
var nlCount int
|
||||||
lastNLPos := -1
|
lastNLPos := -1
|
||||||
for i, r := range t.text[:t.selectionStart] {
|
txt := t.field.TextForRendering()
|
||||||
|
selectionStart, _ := t.field.Selection()
|
||||||
|
if s, _, ok := t.field.CompositionSelection(); ok {
|
||||||
|
selectionStart += s
|
||||||
|
}
|
||||||
|
txt = txt[:selectionStart]
|
||||||
|
for i, r := range txt {
|
||||||
if r == '\n' {
|
if r == '\n' {
|
||||||
nlCount++
|
nlCount++
|
||||||
lastNLPos = i
|
lastNLPos = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
txt := t.text[lastNLPos+1 : t.selectionStart]
|
txt = txt[lastNLPos+1:]
|
||||||
if t.state.Text != "" {
|
|
||||||
txt += t.state.Text[:t.state.CompositionSelectionStartInBytes]
|
|
||||||
}
|
|
||||||
x := int(text.Advance(txt, fontFace))
|
x := int(text.Advance(txt, fontFace))
|
||||||
y := nlCount * int(fontFace.Metrics().HLineGap+fontFace.Metrics().HAscent+fontFace.Metrics().HDescent)
|
y := nlCount * int(fontFace.Metrics().HLineGap+fontFace.Metrics().HAscent+fontFace.Metrics().HDescent)
|
||||||
return x, y
|
return x, y
|
||||||
@ -282,13 +228,14 @@ func (t *TextField) cursorPos() (int, int) {
|
|||||||
func (t *TextField) Draw(screen *ebiten.Image) {
|
func (t *TextField) Draw(screen *ebiten.Image) {
|
||||||
vector.DrawFilledRect(screen, float32(t.bounds.Min.X), float32(t.bounds.Min.Y), float32(t.bounds.Dx()), float32(t.bounds.Dy()), color.White, false)
|
vector.DrawFilledRect(screen, float32(t.bounds.Min.X), float32(t.bounds.Min.Y), float32(t.bounds.Dx()), float32(t.bounds.Dy()), color.White, false)
|
||||||
var clr color.Color = color.Black
|
var clr color.Color = color.Black
|
||||||
if t.focused {
|
if t.field.IsFocused() {
|
||||||
clr = color.RGBA{0, 0, 0xff, 0xff}
|
clr = color.RGBA{0, 0, 0xff, 0xff}
|
||||||
}
|
}
|
||||||
vector.StrokeRect(screen, float32(t.bounds.Min.X), float32(t.bounds.Min.Y), float32(t.bounds.Dx()), float32(t.bounds.Dy()), 1, clr, false)
|
vector.StrokeRect(screen, float32(t.bounds.Min.X), float32(t.bounds.Min.Y), float32(t.bounds.Dx()), float32(t.bounds.Dy()), 1, clr, false)
|
||||||
|
|
||||||
px, py := textFieldPadding()
|
px, py := textFieldPadding()
|
||||||
if t.focused && t.selectionStart >= 0 {
|
selectionStart, _ := t.field.Selection()
|
||||||
|
if t.field.IsFocused() && selectionStart >= 0 {
|
||||||
x, y := t.bounds.Min.X, t.bounds.Min.Y
|
x, y := t.bounds.Min.X, t.bounds.Min.Y
|
||||||
cx, cy := t.cursorPos()
|
cx, cy := t.cursorPos()
|
||||||
x += px + cx
|
x += px + cx
|
||||||
@ -297,18 +244,13 @@ func (t *TextField) Draw(screen *ebiten.Image) {
|
|||||||
vector.StrokeLine(screen, float32(x), float32(y), float32(x), float32(y+h), 1, color.Black, false)
|
vector.StrokeLine(screen, float32(x), float32(y), float32(x), float32(y+h), 1, color.Black, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
shownText := t.text
|
|
||||||
if t.focused && t.state.Text != "" {
|
|
||||||
shownText = t.text[:t.selectionStart] + t.state.Text + t.text[t.selectionEnd:]
|
|
||||||
}
|
|
||||||
|
|
||||||
tx := t.bounds.Min.X + px
|
tx := t.bounds.Min.X + px
|
||||||
ty := t.bounds.Min.Y + py
|
ty := t.bounds.Min.Y + py
|
||||||
op := &text.DrawOptions{}
|
op := &text.DrawOptions{}
|
||||||
op.GeoM.Translate(float64(tx), float64(ty))
|
op.GeoM.Translate(float64(tx), float64(ty))
|
||||||
op.ColorScale.ScaleWithColor(color.Black)
|
op.ColorScale.ScaleWithColor(color.Black)
|
||||||
op.LineSpacing = fontFace.Metrics().HLineGap + fontFace.Metrics().HAscent + fontFace.Metrics().HDescent
|
op.LineSpacing = fontFace.Metrics().HLineGap + fontFace.Metrics().HAscent + fontFace.Metrics().HDescent
|
||||||
text.Draw(screen, shownText, fontFace, op)
|
text.Draw(screen, t.field.TextForRendering(), fontFace, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
const textFieldHeight = 24
|
const textFieldHeight = 24
|
||||||
@ -348,7 +290,9 @@ func (g *Game) Update() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tf := range g.textFields {
|
for _, tf := range g.textFields {
|
||||||
tf.Update()
|
if err := tf.Update(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x, y := ebiten.CursorPosition()
|
x, y := ebiten.CursorPosition()
|
||||||
|
154
exp/textinput/api_windows.go
Normal file
154
exp/textinput/api_windows.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright 2024 The Ebitengine 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.
|
||||||
|
|
||||||
|
package textinput
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ATTR_TARGET_CONVERTED = 0x01
|
||||||
|
_ATTR_TARGET_NOTCONVERTED = 0x03
|
||||||
|
|
||||||
|
_CFS_CANDIDATEPOS = 0x0040
|
||||||
|
|
||||||
|
_GCS_COMPATTR = 0x0010
|
||||||
|
_GCS_COMPCLAUSE = 0x0020
|
||||||
|
_GCS_COMPSTR = 0x0008
|
||||||
|
_GCS_RESULTSTR = 0x0800
|
||||||
|
|
||||||
|
_GWL_WNDPROC = -4
|
||||||
|
|
||||||
|
_ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000
|
||||||
|
|
||||||
|
_UNICODE_NOCHAR = 0xffff
|
||||||
|
|
||||||
|
_WM_CHAR = 0x0102
|
||||||
|
_WM_IME_COMPOSITION = 0x010F
|
||||||
|
_WM_IME_SETCONTEXT = 0x0281
|
||||||
|
_WM_SYSCHAR = 0x0106
|
||||||
|
_WM_UNICHAR = 0x0109
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
_HIMC uintptr
|
||||||
|
)
|
||||||
|
|
||||||
|
type _CANDIDATEFORM struct {
|
||||||
|
dwIndex uint32
|
||||||
|
dwStyle uint32
|
||||||
|
ptCurrentPos _POINT
|
||||||
|
rcArea _RECT
|
||||||
|
}
|
||||||
|
|
||||||
|
type _POINT struct {
|
||||||
|
x int32
|
||||||
|
y int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type _RECT struct {
|
||||||
|
left int32
|
||||||
|
top int32
|
||||||
|
right int32
|
||||||
|
bottom int32
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
imm32 = windows.NewLazySystemDLL("imm32.dll")
|
||||||
|
user32 = windows.NewLazySystemDLL("user32.dll")
|
||||||
|
|
||||||
|
procImmGetCompositionStringW = imm32.NewProc("ImmGetCompositionStringW")
|
||||||
|
procImmGetContext = imm32.NewProc("ImmGetContext")
|
||||||
|
procImmReleaseContext = imm32.NewProc("ImmReleaseContext")
|
||||||
|
procImmSetCandidateWindow = imm32.NewProc("ImmSetCandidateWindow")
|
||||||
|
|
||||||
|
procCallWindowProcW = user32.NewProc("CallWindowProcW")
|
||||||
|
procGetActiveWindow = user32.NewProc("GetActiveWindow")
|
||||||
|
procSetWindowLongW = user32.NewProc("SetWindowLongW") // 32-Bit Windows version.
|
||||||
|
procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") // 64-Bit Windows version.
|
||||||
|
)
|
||||||
|
|
||||||
|
func _CallWindowProcW(lpPrevWndFunc uintptr, hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr {
|
||||||
|
r, _, _ := procCallWindowProcW.Call(lpPrevWndFunc, hWnd, uintptr(msg), wParam, lParam)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func _GetActiveWindow() windows.HWND {
|
||||||
|
r, _, _ := procGetActiveWindow.Call()
|
||||||
|
return windows.HWND(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ImmGetCompositionStringW(unnamedParam1 _HIMC, unnamedParam2 uint32, lpBuf unsafe.Pointer, dwBufLen uint32) (uint32, error) {
|
||||||
|
r, _, e := procImmGetCompositionStringW.Call(uintptr(unnamedParam1), uintptr(unnamedParam2), uintptr(lpBuf), uintptr(dwBufLen))
|
||||||
|
runtime.KeepAlive(lpBuf)
|
||||||
|
if r < 0 {
|
||||||
|
return 0, fmt.Errorf("textinput: ImmGetCompositionStringW failed: %d", r)
|
||||||
|
}
|
||||||
|
if e != nil && e != windows.ERROR_SUCCESS {
|
||||||
|
return 0, fmt.Errorf("textinput: ImmGetCompositionStringW failed: %w", e)
|
||||||
|
}
|
||||||
|
return uint32(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ImmGetContext(unnamedParam1 windows.HWND) _HIMC {
|
||||||
|
r, _, _ := procImmGetContext.Call(uintptr(unnamedParam1))
|
||||||
|
return _HIMC(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ImmReleaseContext(unnamedParam1 windows.HWND, unnamedParam2 _HIMC) error {
|
||||||
|
r, _, e := procImmReleaseContext.Call(uintptr(unnamedParam1), uintptr(unnamedParam2))
|
||||||
|
if int32(r) == 0 {
|
||||||
|
if e != nil && e != windows.ERROR_SUCCESS {
|
||||||
|
return fmt.Errorf("textinput: ImmReleaseContext failed: %w", e)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("textinput: ImmReleaseContext returned 0")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _ImmSetCandidateWindow(unnamedParam1 _HIMC, lpCandidate *_CANDIDATEFORM) error {
|
||||||
|
r, _, e := procImmSetCandidateWindow.Call(uintptr(unnamedParam1), uintptr(unsafe.Pointer(lpCandidate)))
|
||||||
|
runtime.KeepAlive(lpCandidate)
|
||||||
|
if int32(r) == 0 {
|
||||||
|
if e != nil && e != windows.ERROR_SUCCESS {
|
||||||
|
return fmt.Errorf("textinput: ImmSetCandidateWindow failed: %w", e)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("textinput: ImmSetCandidateWindow returned 0")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _SetWindowLongPtrW(hWnd windows.HWND, nIndex int32, dwNewLong uintptr) (uintptr, error) {
|
||||||
|
var p *windows.LazyProc
|
||||||
|
if procSetWindowLongPtrW.Find() == nil {
|
||||||
|
// 64-Bit Windows.
|
||||||
|
p = procSetWindowLongPtrW
|
||||||
|
} else {
|
||||||
|
// 32-Bit Windows.
|
||||||
|
p = procSetWindowLongW
|
||||||
|
}
|
||||||
|
h, _, e := p.Call(uintptr(hWnd), uintptr(nIndex), dwNewLong)
|
||||||
|
if h == 0 {
|
||||||
|
if e != nil && e != windows.ERROR_SUCCESS {
|
||||||
|
return 0, fmt.Errorf("textinput: SetWindowLongPtrW failed: %w", e)
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("textinput: SetWindowLongPtrW returned 0")
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
246
exp/textinput/field.go
Normal file
246
exp/textinput/field.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
// Copyright 2024 The Ebitengine 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.
|
||||||
|
|
||||||
|
package textinput
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
theFocusedField *Field
|
||||||
|
theFocusedFieldM sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func focusField(f *Field) {
|
||||||
|
var origField *Field
|
||||||
|
defer func() {
|
||||||
|
if origField != nil {
|
||||||
|
origField.cleanUp()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
theFocusedFieldM.Lock()
|
||||||
|
defer theFocusedFieldM.Unlock()
|
||||||
|
if theFocusedField == f {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origField = theFocusedField
|
||||||
|
theFocusedField = f
|
||||||
|
}
|
||||||
|
|
||||||
|
func blurField(f *Field) {
|
||||||
|
var origField *Field
|
||||||
|
defer func() {
|
||||||
|
if origField != nil {
|
||||||
|
origField.cleanUp()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
theFocusedFieldM.Lock()
|
||||||
|
defer theFocusedFieldM.Unlock()
|
||||||
|
if theFocusedField != f {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
origField = theFocusedField
|
||||||
|
theFocusedField = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFieldFocused(f *Field) bool {
|
||||||
|
theFocusedFieldM.Lock()
|
||||||
|
defer theFocusedFieldM.Unlock()
|
||||||
|
return theFocusedField == f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field is a region accepting text inputting with IME.
|
||||||
|
//
|
||||||
|
// Field is not focused by default. You have to call Focus when you start text inputting.
|
||||||
|
//
|
||||||
|
// Field is a wrapper of the low-level API like Start.
|
||||||
|
//
|
||||||
|
// For an actual usage, see the examples "textinput".
|
||||||
|
type Field struct {
|
||||||
|
text string
|
||||||
|
selectionStart int
|
||||||
|
selectionEnd int
|
||||||
|
|
||||||
|
ch chan State
|
||||||
|
end func()
|
||||||
|
state State
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleInput updates the field state.
|
||||||
|
// HandleInput must be called every tick, i.e., every HandleInput, when Field is focused.
|
||||||
|
// HandleInput takes a position where an IME window is shown if needed.
|
||||||
|
//
|
||||||
|
// HandleInput returns whether the text inputting is handled or not.
|
||||||
|
// If HandleInput returns true, a Field user should not handle further input events.
|
||||||
|
//
|
||||||
|
// HandleInput returns an error when handling input causes an error.
|
||||||
|
func (f *Field) HandleInput(x, y int) (handled bool, err error) {
|
||||||
|
if f.err != nil {
|
||||||
|
return false, f.err
|
||||||
|
}
|
||||||
|
if !f.IsFocused() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text inputting can happen multiple times in one tick (1/60[s] by default).
|
||||||
|
// Handle all of them.
|
||||||
|
for {
|
||||||
|
if f.ch == nil {
|
||||||
|
// TODO: On iOS Safari, Start doesn't work as expected (#2898).
|
||||||
|
// Handle a click event and focus the textarea there.
|
||||||
|
f.ch, f.end = Start(x, y)
|
||||||
|
// Start returns nil for non-supported envrionments.
|
||||||
|
if f.ch == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readchar:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case state, ok := <-f.ch:
|
||||||
|
if state.Error != nil {
|
||||||
|
f.err = state.Error
|
||||||
|
return false, f.err
|
||||||
|
}
|
||||||
|
handled = true
|
||||||
|
if !ok {
|
||||||
|
f.ch = nil
|
||||||
|
f.end = nil
|
||||||
|
f.state = State{}
|
||||||
|
break readchar
|
||||||
|
}
|
||||||
|
if state.Committed {
|
||||||
|
f.text = f.text[:f.selectionStart] + state.Text + f.text[f.selectionEnd:]
|
||||||
|
f.selectionStart += len(state.Text)
|
||||||
|
f.selectionEnd = f.selectionStart
|
||||||
|
f.state = State{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.state = state
|
||||||
|
default:
|
||||||
|
break readchar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.ch == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus focuses the field.
|
||||||
|
// A Field has to be focused to start text inputting.
|
||||||
|
//
|
||||||
|
// There can be only one Field that is focused at the same time.
|
||||||
|
// When Focus is called and there is already a focused field, Focus removes the focus of that.
|
||||||
|
func (f *Field) Focus() {
|
||||||
|
focusField(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blur removes the focus from the field.
|
||||||
|
func (f *Field) Blur() {
|
||||||
|
blurField(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFocused reports whether the field is focused or not.
|
||||||
|
func (f *Field) IsFocused() bool {
|
||||||
|
return isFieldFocused(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) cleanUp() {
|
||||||
|
if f.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the text field still has a session, read the last state and process it just in case.
|
||||||
|
if f.ch != nil {
|
||||||
|
select {
|
||||||
|
case state, ok := <-f.ch:
|
||||||
|
if state.Error != nil {
|
||||||
|
f.err = state.Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ok && state.Committed {
|
||||||
|
f.text = f.text[:f.selectionStart] + state.Text + f.text[f.selectionEnd:]
|
||||||
|
f.selectionStart += len(state.Text)
|
||||||
|
f.selectionEnd = f.selectionStart
|
||||||
|
f.state = State{}
|
||||||
|
}
|
||||||
|
f.state = state
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.end != nil {
|
||||||
|
f.end()
|
||||||
|
f.ch = nil
|
||||||
|
f.end = nil
|
||||||
|
f.state = State{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection returns the current selection range in bytes.
|
||||||
|
func (f *Field) Selection() (start, end int) {
|
||||||
|
return f.selectionStart, f.selectionEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompositionSelection returns the current composition selection in bytes if a text is composited.
|
||||||
|
// If a text is not composited, this returns 0s and false.
|
||||||
|
// The returned values indicate relative positions in bytes where the current composition text's start is 0.
|
||||||
|
func (f *Field) CompositionSelection() (start, end int, ok bool) {
|
||||||
|
if f.IsFocused() && f.state.Text != "" {
|
||||||
|
return f.state.CompositionSelectionStartInBytes, f.state.CompositionSelectionEndInBytes, true
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSelection sets the selection range.
|
||||||
|
func (f *Field) SetSelection(start, end int) {
|
||||||
|
f.cleanUp()
|
||||||
|
f.selectionStart = start
|
||||||
|
f.selectionEnd = end
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text returns the current text.
|
||||||
|
// The returned value doesn't include compositing texts.
|
||||||
|
func (f *Field) Text() string {
|
||||||
|
return f.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextForRendering returns the text for rendering.
|
||||||
|
// The returned value includes compositing texts.
|
||||||
|
func (f *Field) TextForRendering() string {
|
||||||
|
if f.IsFocused() && f.state.Text != "" {
|
||||||
|
return f.text[:f.selectionStart] + f.state.Text + f.text[f.selectionEnd:]
|
||||||
|
}
|
||||||
|
return f.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTextAndSelection sets the text and the selection range.
|
||||||
|
func (f *Field) SetTextAndSelection(text string, selectionStart, selectionEnd int) {
|
||||||
|
f.cleanUp()
|
||||||
|
f.text = text
|
||||||
|
f.selectionStart = selectionStart
|
||||||
|
f.selectionEnd = selectionEnd
|
||||||
|
}
|
@ -25,6 +25,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// State represents the current state of text inputting.
|
// State represents the current state of text inputting.
|
||||||
|
//
|
||||||
|
// State is the low-level API. For most use cases, Field is easier to use.
|
||||||
type State struct {
|
type State struct {
|
||||||
// Text represents the current inputting text.
|
// Text represents the current inputting text.
|
||||||
Text string
|
Text string
|
||||||
@ -37,14 +39,19 @@ type State struct {
|
|||||||
|
|
||||||
// Committed reports whether the current Text is the settled text.
|
// Committed reports whether the current Text is the settled text.
|
||||||
Committed bool
|
Committed bool
|
||||||
|
|
||||||
|
// Error is an error that happens during text inputting.
|
||||||
|
Error error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts text inputting.
|
// Start starts text inputting.
|
||||||
// Start returns a channel to send the state repeatedly, and a function to end the text inputting.
|
// Start returns a channel to send the state repeatedly, and a function to end the text inputting.
|
||||||
//
|
//
|
||||||
|
// Start is the low-leve API. For most use cases, Field is easier to use.
|
||||||
|
//
|
||||||
// Start returns nil and nil if the current environment doesn't support this package.
|
// Start returns nil and nil if the current environment doesn't support this package.
|
||||||
func Start(x, y int) (states chan State, close func()) {
|
func Start(x, y int) (states chan State, close func()) {
|
||||||
cx, cy := ui.Get().LogicalPositionToClientPosition(float64(x), float64(y))
|
cx, cy := ui.Get().LogicalPositionToClientPositionInNativePixels(float64(x), float64(y))
|
||||||
return theTextInput.Start(int(cx), int(cy))
|
return theTextInput.Start(int(cx), int(cy))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +59,7 @@ var theTextInput textInput
|
|||||||
func (t *textInput) Start(x, y int) (chan State, func()) {
|
func (t *textInput) Start(x, y int) (chan State, func()) {
|
||||||
var session *session
|
var session *session
|
||||||
ui.Get().RunOnMainThread(func() {
|
ui.Get().RunOnMainThread(func() {
|
||||||
if t.session != nil {
|
t.end()
|
||||||
t.session.end()
|
|
||||||
t.session = nil
|
|
||||||
}
|
|
||||||
C.start(C.int(x), C.int(y))
|
C.start(C.int(x), C.int(y))
|
||||||
session = newSession()
|
session = newSession()
|
||||||
t.session = session
|
t.session = session
|
||||||
|
@ -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.
|
||||||
|
|
||||||
//go:build (!darwin && !js) || ios
|
//go:build (!darwin && !js && !windows) || ios
|
||||||
|
|
||||||
package textinput
|
package textinput
|
||||||
|
|
||||||
|
263
exp/textinput/textinput_windows.go
Normal file
263
exp/textinput/textinput_windows.go
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
// Copyright 2024 The Ebitengine 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.
|
||||||
|
|
||||||
|
package textinput
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type textInput struct {
|
||||||
|
session *session
|
||||||
|
|
||||||
|
origWndProc uintptr
|
||||||
|
wndProcCallback uintptr
|
||||||
|
window windows.HWND
|
||||||
|
|
||||||
|
highSurrogate uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var theTextInput textInput
|
||||||
|
|
||||||
|
func (t *textInput) Start(x, y int) (chan State, func()) {
|
||||||
|
if microsoftgdk.IsXbox() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var session *session
|
||||||
|
var err error
|
||||||
|
ui.Get().RunOnMainThread(func() {
|
||||||
|
t.end()
|
||||||
|
err = t.start(x, y)
|
||||||
|
session = newSession()
|
||||||
|
t.session = session
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
session.ch <- State{Error: err}
|
||||||
|
session.end()
|
||||||
|
}
|
||||||
|
return session.ch, session.end
|
||||||
|
}
|
||||||
|
|
||||||
|
// start must be called from the main thread.
|
||||||
|
func (t *textInput) start(x, y int) error {
|
||||||
|
if t.window == 0 {
|
||||||
|
t.window = _GetActiveWindow()
|
||||||
|
}
|
||||||
|
if t.origWndProc == 0 {
|
||||||
|
if t.wndProcCallback == 0 {
|
||||||
|
t.wndProcCallback = windows.NewCallback(t.wndProc)
|
||||||
|
}
|
||||||
|
// Note that a Win32API GetActiveWindow doesn't work on Xbox.
|
||||||
|
h, err := _SetWindowLongPtrW(t.window, _GWL_WNDPROC, t.wndProcCallback)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.origWndProc = h
|
||||||
|
}
|
||||||
|
|
||||||
|
h := _ImmGetContext(t.window)
|
||||||
|
if err := _ImmSetCandidateWindow(h, &_CANDIDATEFORM{
|
||||||
|
dwIndex: 0,
|
||||||
|
dwStyle: _CFS_CANDIDATEPOS,
|
||||||
|
ptCurrentPos: _POINT{
|
||||||
|
x: int32(x),
|
||||||
|
y: int32(y),
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := _ImmReleaseContext(t.window, h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *textInput) wndProc(hWnd uintptr, uMsg uint32, wParam, lParam uintptr) uintptr {
|
||||||
|
if t.session == nil {
|
||||||
|
return _CallWindowProcW(t.origWndProc, hWnd, uMsg, wParam, lParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch uMsg {
|
||||||
|
case _WM_IME_SETCONTEXT:
|
||||||
|
// Draw preedit text by an application side.
|
||||||
|
if lParam&_ISC_SHOWUICOMPOSITIONWINDOW != 0 {
|
||||||
|
lParam &^= _ISC_SHOWUICOMPOSITIONWINDOW
|
||||||
|
}
|
||||||
|
case _WM_IME_COMPOSITION:
|
||||||
|
if lParam&(_GCS_RESULTSTR|_GCS_COMPSTR) != 0 {
|
||||||
|
if lParam&_GCS_RESULTSTR != 0 {
|
||||||
|
if err := t.commit(); err != nil {
|
||||||
|
t.session.ch <- State{Error: err}
|
||||||
|
t.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lParam&_GCS_COMPSTR != 0 {
|
||||||
|
if err := t.update(); err != nil {
|
||||||
|
t.session.ch <- State{Error: err}
|
||||||
|
t.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
case _WM_CHAR, _WM_SYSCHAR:
|
||||||
|
if wParam >= 0xd800 && wParam <= 0xdbff {
|
||||||
|
t.highSurrogate = uint16(wParam)
|
||||||
|
} else {
|
||||||
|
var c rune
|
||||||
|
if wParam >= 0xdc00 && wParam <= 0xdfff {
|
||||||
|
if t.highSurrogate != 0 {
|
||||||
|
c += (rune(t.highSurrogate) - 0xd800) << 10
|
||||||
|
c += (rune(wParam) & 0xffff) - 0xdc00
|
||||||
|
c += 0x10000
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c = rune(wParam) & 0xffff
|
||||||
|
}
|
||||||
|
t.highSurrogate = 0
|
||||||
|
if c >= 0x20 {
|
||||||
|
str := string(c)
|
||||||
|
t.send(str, 0, len(str), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _WM_UNICHAR:
|
||||||
|
if wParam == _UNICODE_NOCHAR {
|
||||||
|
// WM_UNICHAR is not sent by Windows, but is sent by some third-party input method engine.
|
||||||
|
// Returning TRUE here announces support for this message.
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if r := rune(wParam); r >= 0x20 {
|
||||||
|
str := string(r)
|
||||||
|
t.send(str, 0, len(str), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _CallWindowProcW(t.origWndProc, hWnd, uMsg, wParam, lParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send must be called from the main thread.
|
||||||
|
func (t *textInput) send(text string, startInBytes, endInBytes int, committed bool) {
|
||||||
|
if t.session != nil {
|
||||||
|
t.session.trySend(State{
|
||||||
|
Text: text,
|
||||||
|
CompositionSelectionStartInBytes: startInBytes,
|
||||||
|
CompositionSelectionEndInBytes: endInBytes,
|
||||||
|
Committed: committed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if committed {
|
||||||
|
t.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// end must be called from the main thread.
|
||||||
|
func (t *textInput) end() {
|
||||||
|
if t.session != nil {
|
||||||
|
t.session.end()
|
||||||
|
t.session = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update must be called from the main thread.
|
||||||
|
func (t *textInput) update() (ferr error) {
|
||||||
|
hIMC := _ImmGetContext(t.window)
|
||||||
|
defer func() {
|
||||||
|
if err := _ImmReleaseContext(t.window, hIMC); err != nil && ferr != nil {
|
||||||
|
ferr = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bufferLen, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPSTR, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bufferLen == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer16 := make([]uint16, bufferLen/uint32(unsafe.Sizeof(uint16(0))))
|
||||||
|
if _, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPSTR, unsafe.Pointer(&buffer16[0]), bufferLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
attrLen, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPATTR, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attr := make([]byte, attrLen)
|
||||||
|
if _, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPATTR, unsafe.Pointer(&attr[0]), attrLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clauseLen, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPCLAUSE, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
clause := make([]uint32, clauseLen/uint32(unsafe.Sizeof(uint32(0))))
|
||||||
|
if _, err := _ImmGetCompositionStringW(hIMC, _GCS_COMPCLAUSE, unsafe.Pointer(&clause[0]), clauseLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var start16 int
|
||||||
|
var end16 int
|
||||||
|
if len(clause) > 0 {
|
||||||
|
for i, c := range clause[:len(clause)-1] {
|
||||||
|
if int(c) == len(attr) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if attr[c] == _ATTR_TARGET_CONVERTED || attr[c] == _ATTR_TARGET_NOTCONVERTED {
|
||||||
|
start16 = int(c)
|
||||||
|
end16 = int(clause[i+1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text := windows.UTF16ToString(buffer16)
|
||||||
|
t.send(text, convertUTF16CountToByteCount(text, start16), convertUTF16CountToByteCount(text, end16), false)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// commit must be called from the main thread.
|
||||||
|
func (t *textInput) commit() (ferr error) {
|
||||||
|
hIMC := _ImmGetContext(t.window)
|
||||||
|
defer func() {
|
||||||
|
if err := _ImmReleaseContext(t.window, hIMC); err != nil && ferr != nil {
|
||||||
|
ferr = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
bufferLen, err := _ImmGetCompositionStringW(hIMC, _GCS_RESULTSTR, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bufferLen == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer16 := make([]uint16, bufferLen/uint32(unsafe.Sizeof(uint16(0))))
|
||||||
|
if _, err := _ImmGetCompositionStringW(hIMC, _GCS_RESULTSTR, unsafe.Pointer(&buffer16[0]), bufferLen); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := windows.UTF16ToString(buffer16)
|
||||||
|
t.send(text, 0, len(text), true)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
15
genkeys.go
15
genkeys.go
@ -140,9 +140,17 @@ func init() {
|
|||||||
"NumpadSubtract": "KPSubtract",
|
"NumpadSubtract": "KPSubtract",
|
||||||
"NumpadEnter": "KPEnter",
|
"NumpadEnter": "KPEnter",
|
||||||
"NumpadEqual": "KPEqual",
|
"NumpadEqual": "KPEqual",
|
||||||
|
"IntlBackslash": "World1",
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developer.android.com/reference/android/view/KeyEvent
|
// https://developer.android.com/reference/android/view/KeyEvent
|
||||||
|
//
|
||||||
|
// Android doesn't distinguish these keys:
|
||||||
|
// - a US backslash key (HID: 0x31),
|
||||||
|
// - an international pound/tilde key (HID: 0x32), and
|
||||||
|
// - an international backslash key (HID: 0x64).
|
||||||
|
// These are mapped to the same key code KEYCODE_BACKSLASH (73).
|
||||||
|
// See https://source.android.com/docs/core/interaction/input/keyboard-devices
|
||||||
androidKeyToUIKeyName = map[int]string{
|
androidKeyToUIKeyName = map[int]string{
|
||||||
55: "Comma",
|
55: "Comma",
|
||||||
56: "Period",
|
56: "Period",
|
||||||
@ -204,16 +212,16 @@ func init() {
|
|||||||
0x35: "Backquote",
|
0x35: "Backquote",
|
||||||
|
|
||||||
// These three keys are:
|
// These three keys are:
|
||||||
// - US backslash-pipe key (above return),
|
// - US backslash-pipe key, and
|
||||||
// - non-US backslash key (next to left shift; on German layout this is the <>| key), and
|
|
||||||
// - non-US hashmark key (bottom left of return; on German layout, this is the #' key).
|
// - non-US hashmark key (bottom left of return; on German layout, this is the #' key).
|
||||||
// On US layout configurations, they all map to the same characters - the backslash.
|
// On US layout configurations, they all map to the same characters - the backslash.
|
||||||
//
|
//
|
||||||
// See also: https://www.w3.org/TR/uievents-code/#keyboard-102
|
// See also: https://www.w3.org/TR/uievents-code/#keyboard-102
|
||||||
0x31: "Backslash", // UIKeyboardHIDUsageKeyboardBackslash
|
0x31: "Backslash", // UIKeyboardHIDUsageKeyboardBackslash
|
||||||
0x64: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSBackslash
|
|
||||||
0x32: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSPound
|
0x32: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSPound
|
||||||
|
|
||||||
|
0x64: "IntlBackslash", // UIKeyboardHIDUsageKeyboardNonUSBackslash
|
||||||
|
|
||||||
0x2A: "Backspace",
|
0x2A: "Backspace",
|
||||||
0x2F: "BracketLeft",
|
0x2F: "BracketLeft",
|
||||||
0x30: "BracketRight",
|
0x30: "BracketRight",
|
||||||
@ -326,6 +334,7 @@ func init() {
|
|||||||
"NumpadEqual": "NumpadEqual",
|
"NumpadEqual": "NumpadEqual",
|
||||||
"MetaLeft": "MetaLeft",
|
"MetaLeft": "MetaLeft",
|
||||||
"MetaRight": "MetaRight",
|
"MetaRight": "MetaRight",
|
||||||
|
"IntlBackslash": "IntlBackslash",
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
8
go.mod
8
go.mod
@ -3,11 +3,11 @@ module github.com/hajimehoshi/ebiten/v2
|
|||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ebitengine/gomobile v0.0.0-20240223151600-9f1d75a9f41c
|
github.com/ebitengine/gomobile v0.0.0-20240318151619-0eadfb33c201
|
||||||
github.com/ebitengine/hideconsole v1.0.0
|
github.com/ebitengine/hideconsole v1.0.0
|
||||||
github.com/ebitengine/oto/v3 v3.2.0-alpha.4
|
github.com/ebitengine/oto/v3 v3.2.0-alpha.4
|
||||||
github.com/ebitengine/purego v0.7.0-alpha.1
|
github.com/ebitengine/purego v0.7.0-alpha.3
|
||||||
github.com/go-text/typesetting v0.1.1-0.20231231232151-8d81c02dc157
|
github.com/go-text/typesetting v0.1.1-0.20240317203452-bc341f663203
|
||||||
github.com/hajimehoshi/bitmapfont/v3 v3.0.0
|
github.com/hajimehoshi/bitmapfont/v3 v3.0.0
|
||||||
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
|
||||||
@ -16,7 +16,7 @@ require (
|
|||||||
github.com/kisielk/errcheck v1.6.3
|
github.com/kisielk/errcheck v1.6.3
|
||||||
golang.org/x/image v0.15.0
|
golang.org/x/image v0.15.0
|
||||||
golang.org/x/sync v0.6.0
|
golang.org/x/sync v0.6.0
|
||||||
golang.org/x/sys v0.17.0
|
golang.org/x/sys v0.18.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
golang.org/x/tools v0.18.0
|
golang.org/x/tools v0.18.0
|
||||||
)
|
)
|
||||||
|
18
go.sum
18
go.sum
@ -1,14 +1,14 @@
|
|||||||
github.com/ebitengine/gomobile v0.0.0-20240223151600-9f1d75a9f41c h1:fIrdax248gvTVn/QL+U0taS0Fs9SVKKSpXxiibMs6p4=
|
github.com/ebitengine/gomobile v0.0.0-20240318151619-0eadfb33c201 h1:QlcxUcnQjv62kMxsh0NAQpwE0P454ec2i82DxmthMeI=
|
||||||
github.com/ebitengine/gomobile v0.0.0-20240223151600-9f1d75a9f41c/go.mod h1:8SdR2+sMMmYsei+c0ZW+AmJx2W6dPeSixWWSKRkJppA=
|
github.com/ebitengine/gomobile v0.0.0-20240318151619-0eadfb33c201/go.mod h1:8SdR2+sMMmYsei+c0ZW+AmJx2W6dPeSixWWSKRkJppA=
|
||||||
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.2.0-alpha.4 h1:aaUdcbEDUV1oErHDv/Cd0IAjQaQPChZuvO8Cn/kQHE8=
|
github.com/ebitengine/oto/v3 v3.2.0-alpha.4 h1:aaUdcbEDUV1oErHDv/Cd0IAjQaQPChZuvO8Cn/kQHE8=
|
||||||
github.com/ebitengine/oto/v3 v3.2.0-alpha.4/go.mod h1:JtMbxJHZBDXfS8BmVYwzWk9Z6r7jsjwsHzOuZrEkfs4=
|
github.com/ebitengine/oto/v3 v3.2.0-alpha.4/go.mod h1:JtMbxJHZBDXfS8BmVYwzWk9Z6r7jsjwsHzOuZrEkfs4=
|
||||||
github.com/ebitengine/purego v0.7.0-alpha.1 h1:Dlm9jM2kuzVQS89S1AALkDZQquow/Bbn3KVG68tFtWU=
|
github.com/ebitengine/purego v0.7.0-alpha.3 h1:9hH1aneqLaM3sM+PMUgRJVsMe2SqfVjZtV3DEzxBDJU=
|
||||||
github.com/ebitengine/purego v0.7.0-alpha.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
github.com/ebitengine/purego v0.7.0-alpha.3/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||||
github.com/go-text/typesetting v0.1.1-0.20231231232151-8d81c02dc157 h1:gNNO+Nvt8Kkg+VWN7qKvV02f+0pxH9zdKDMTixfn17Q=
|
github.com/go-text/typesetting v0.1.1-0.20240317203452-bc341f663203 h1:dDnsjfTamLS74H+chc/GvLDFboY9BlK0Ztg5ibOzZ34=
|
||||||
github.com/go-text/typesetting v0.1.1-0.20231231232151-8d81c02dc157/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
github.com/go-text/typesetting v0.1.1-0.20240317203452-bc341f663203/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/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4=
|
github.com/hajimehoshi/bitmapfont/v3 v3.0.0 h1:r2+6gYK38nfztS/et50gHAswb9hXgxXECYgE8Nczmi4=
|
||||||
github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA=
|
github.com/hajimehoshi/bitmapfont/v3 v3.0.0/go.mod h1:+CxxG+uMmgU4mI2poq944i3uZ6UYFfAkj9V6WqmuvZA=
|
||||||
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68=
|
||||||
@ -54,8 +54,8 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
12
image.go
12
image.go
@ -587,6 +587,9 @@ var _ [len(DrawTrianglesShaderOptions{}.Images) - graphics.ShaderImageCount]stru
|
|||||||
//
|
//
|
||||||
// If a specified uniform variable's length or type doesn't match with an expected one, DrawTrianglesShader panics.
|
// If a specified uniform variable's length or type doesn't match with an expected one, DrawTrianglesShader panics.
|
||||||
//
|
//
|
||||||
|
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
|
||||||
|
// the value is kept and is not clamped.
|
||||||
|
//
|
||||||
// When the image i is disposed, DrawTrianglesShader does nothing.
|
// When the image i is disposed, DrawTrianglesShader does nothing.
|
||||||
func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
|
func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
|
||||||
i.copyCheck()
|
i.copyCheck()
|
||||||
@ -742,6 +745,9 @@ var _ [len(DrawRectShaderOptions{}.Images)]struct{} = [graphics.ShaderImageCount
|
|||||||
// If no source images are specified, imageSrc0Size returns a valid size only when the unit is pixels,
|
// If no source images are specified, imageSrc0Size returns a valid size only when the unit is pixels,
|
||||||
// but always returns 0 when the unit is texels (default).
|
// but always returns 0 when the unit is texels (default).
|
||||||
//
|
//
|
||||||
|
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
|
||||||
|
// the value is kept and is not clamped.
|
||||||
|
//
|
||||||
// When the image i is disposed, DrawRectShader does nothing.
|
// When the image i is disposed, DrawRectShader does nothing.
|
||||||
func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawRectShaderOptions) {
|
func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawRectShaderOptions) {
|
||||||
i.copyCheck()
|
i.copyCheck()
|
||||||
@ -954,6 +960,9 @@ func (i *Image) at(x, y int) (r, g, b, a byte) {
|
|||||||
//
|
//
|
||||||
// Set implements the standard draw.Image's Set.
|
// Set implements the standard draw.Image's Set.
|
||||||
//
|
//
|
||||||
|
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
|
||||||
|
// the value is kept and is not clamped.
|
||||||
|
//
|
||||||
// If the image is disposed, Set does nothing.
|
// If the image is disposed, Set does nothing.
|
||||||
func (i *Image) Set(x, y int, clr color.Color) {
|
func (i *Image) Set(x, y int, clr color.Color) {
|
||||||
i.copyCheck()
|
i.copyCheck()
|
||||||
@ -1028,6 +1037,9 @@ func (i *Image) Deallocate() {
|
|||||||
//
|
//
|
||||||
// WritePixels also works on a sub-image.
|
// WritePixels also works on a sub-image.
|
||||||
//
|
//
|
||||||
|
// Even if a result is an invalid color as a premultiplied-alpha color, i.e. an alpha value exceeds other color values,
|
||||||
|
// the value is kept and is not clamped.
|
||||||
|
//
|
||||||
// When the image is disposed, WritePixels does nothing.
|
// When the image is disposed, WritePixels does nothing.
|
||||||
func (i *Image) WritePixels(pixels []byte) {
|
func (i *Image) WritePixels(pixels []byte) {
|
||||||
i.copyCheck()
|
i.copyCheck()
|
||||||
|
@ -4582,3 +4582,44 @@ func TestImageDrawImageAfterDeallocation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue #2798
|
||||||
|
func TestImageInvalidPremultipliedAlphaColor(t *testing.T) {
|
||||||
|
// This test checks the rendering result when Set and WritePixels use an invalid premultiplied alpha color.
|
||||||
|
// The result values are kept and not clamped.
|
||||||
|
|
||||||
|
const (
|
||||||
|
w = 16
|
||||||
|
h = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
dst := ebiten.NewImage(w, h)
|
||||||
|
dst.Set(0, 0, color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x40})
|
||||||
|
dst.Set(0, 1, color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x00})
|
||||||
|
if got, want := dst.At(0, 0).(color.RGBA), (color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x40}); got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
if got, want := dst.At(0, 1).(color.RGBA), (color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x00}); got != want {
|
||||||
|
t.Errorf("got: %v, want: %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
pix := make([]byte, 4*w*h)
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
pix[4*(j*16+i)] = byte(i)
|
||||||
|
pix[4*(j*16+i)+1] = byte(j)
|
||||||
|
pix[4*(j*16+i)+2] = 0x80
|
||||||
|
pix[4*(j*16+i)+3] = byte(i - j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst.WritePixels(pix)
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
got := dst.At(i, j)
|
||||||
|
want := color.RGBA{R: byte(i), G: byte(j), B: 0x80, A: byte(i - j)}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -45,6 +45,7 @@ func (g *gamepads) addAndroidGamepad(androidDeviceID int, name, sdlID string, ax
|
|||||||
gp := g.add(name, sdlID)
|
gp := g.add(name, sdlID)
|
||||||
gp.native = &nativeGamepadImpl{
|
gp.native = &nativeGamepadImpl{
|
||||||
androidDeviceID: androidDeviceID,
|
androidDeviceID: androidDeviceID,
|
||||||
|
axesReady: make([]bool, axisCount),
|
||||||
axes: make([]float64, axisCount),
|
axes: make([]float64, axisCount),
|
||||||
buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1),
|
buttons: make([]bool, gamepaddb.SDLControllerButtonMax+1),
|
||||||
hats: make([]int, hatCount),
|
hats: make([]int, hatCount),
|
||||||
@ -108,6 +109,13 @@ func (g *Gamepad) updateAndroidGamepadAxis(axis int, value float64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
n.axes[axis] = value
|
n.axes[axis] = value
|
||||||
|
|
||||||
|
// MotionEvent with 0 value can be sent when a gamepad is connected even though an axis is not touched (#2598).
|
||||||
|
// This is problematic when an axis is a trigger button where -1 should be the default value.
|
||||||
|
// When MotionEvent with non-0 value is sent, it seems fine to assume that the axis is actually touched and ready.
|
||||||
|
if value != 0 {
|
||||||
|
n.axesReady[axis] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) {
|
func (g *Gamepad) updateAndroidGamepadButton(button Button, pressed bool) {
|
||||||
|
@ -243,6 +243,7 @@ type nativeGamepad interface {
|
|||||||
axisCount() int
|
axisCount() int
|
||||||
buttonCount() int
|
buttonCount() int
|
||||||
hatCount() int
|
hatCount() int
|
||||||
|
isAxisReady(axis int) bool
|
||||||
axisValue(axis int) float64
|
axisValue(axis int) float64
|
||||||
buttonValue(button int) float64
|
buttonValue(button int) float64
|
||||||
isButtonPressed(button int) bool
|
isButtonPressed(button int) bool
|
||||||
@ -296,6 +297,14 @@ func (g *Gamepad) HatCount() int {
|
|||||||
return g.native.hatCount()
|
return g.native.hatCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAxisReady is concurrent-safe.
|
||||||
|
func (g *Gamepad) IsAxisReady(axis int) bool {
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
|
return g.native.isAxisReady(axis)
|
||||||
|
}
|
||||||
|
|
||||||
// Axis is concurrent-safe.
|
// Axis is concurrent-safe.
|
||||||
func (g *Gamepad) Axis(axis int) float64 {
|
func (g *Gamepad) Axis(axis int) float64 {
|
||||||
g.m.Lock()
|
g.m.Lock()
|
||||||
@ -356,8 +365,13 @@ func (g *Gamepad) IsStandardButtonAvailable(button gamepaddb.StandardButton) boo
|
|||||||
// StandardAxisValue is concurrent-safe.
|
// StandardAxisValue is concurrent-safe.
|
||||||
func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
|
func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
|
||||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||||
return gamepaddb.AxisValue(g.sdlID, axis, g)
|
// StandardAxisValue invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
|
||||||
|
return gamepaddb.StandardAxisValue(g.sdlID, axis, g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
if m := g.native.standardAxisInOwnMapping(axis); m != nil {
|
if m := g.native.standardAxisInOwnMapping(axis); m != nil {
|
||||||
return m.Value()*2 - 1
|
return m.Value()*2 - 1
|
||||||
}
|
}
|
||||||
@ -367,8 +381,13 @@ func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
|
|||||||
// StandardButtonValue is concurrent-safe.
|
// StandardButtonValue is concurrent-safe.
|
||||||
func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
|
func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
|
||||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||||
return gamepaddb.ButtonValue(g.sdlID, button, g)
|
// StandardButtonValue invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
|
||||||
|
return gamepaddb.StandardButtonValue(g.sdlID, button, g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
if m := g.native.standardButtonInOwnMapping(button); m != nil {
|
if m := g.native.standardButtonInOwnMapping(button); m != nil {
|
||||||
return m.Value()
|
return m.Value()
|
||||||
}
|
}
|
||||||
@ -378,8 +397,13 @@ func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
|
|||||||
// IsStandardButtonPressed is concurrent-safe.
|
// IsStandardButtonPressed is concurrent-safe.
|
||||||
func (g *Gamepad) IsStandardButtonPressed(button gamepaddb.StandardButton) bool {
|
func (g *Gamepad) IsStandardButtonPressed(button gamepaddb.StandardButton) bool {
|
||||||
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
if gamepaddb.HasStandardLayoutMapping(g.sdlID) {
|
||||||
return gamepaddb.IsButtonPressed(g.sdlID, button, g)
|
// IsStandardButtonPressed invokes g.Axis, g.Button, or g.Hat so this cannot be locked.
|
||||||
|
return gamepaddb.IsStandardButtonPressed(g.sdlID, button, g)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.m.Lock()
|
||||||
|
defer g.m.Unlock()
|
||||||
|
|
||||||
if m := g.native.standardButtonInOwnMapping(button); m != nil {
|
if m := g.native.standardButtonInOwnMapping(button); m != nil {
|
||||||
return m.Pressed()
|
return m.Pressed()
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,10 @@ func (*nativeGamepadsImpl) update(gamepads *gamepads) error {
|
|||||||
type nativeGamepadImpl struct {
|
type nativeGamepadImpl struct {
|
||||||
androidDeviceID int
|
androidDeviceID int
|
||||||
|
|
||||||
axes []float64
|
axesReady []bool
|
||||||
buttons []bool
|
axes []float64
|
||||||
hats []int
|
buttons []bool
|
||||||
|
hats []int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
|
func (*nativeGamepadImpl) update(gamepad *gamepads) error {
|
||||||
@ -71,6 +72,13 @@ func (g *nativeGamepadImpl) hatCount() int {
|
|||||||
return len(g.hats)
|
return len(g.hats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
if axis < 0 || axis >= len(g.axesReady) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return g.axesReady[axis]
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
if axis < 0 || axis >= len(g.axes) {
|
if axis < 0 || axis >= len(g.axes) {
|
||||||
return 0
|
return 0
|
||||||
|
@ -399,6 +399,10 @@ func (g *nativeGamepadImpl) hatCount() int {
|
|||||||
return len(g.hatValues)
|
return len(g.hatValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
if axis < 0 || axis >= len(g.axisValues) {
|
if axis < 0 || axis >= len(g.axisValues) {
|
||||||
return 0
|
return 0
|
||||||
|
@ -719,6 +719,10 @@ func (g *nativeGamepadDesktop) hatCount() int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadDesktop) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadDesktop) axisValue(axis int) float64 {
|
func (g *nativeGamepadDesktop) axisValue(axis int) float64 {
|
||||||
if g.usesDInput() {
|
if g.usesDInput() {
|
||||||
if axis < 0 || axis >= len(g.dinputAxes) {
|
if axis < 0 || axis >= len(g.dinputAxes) {
|
||||||
|
@ -76,6 +76,10 @@ func (g *nativeGamepadImpl) hatCount() int {
|
|||||||
return len(g.hats)
|
return len(g.hats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
if axis < 0 || axis >= len(g.axes) {
|
if axis < 0 || axis >= len(g.axes) {
|
||||||
return 0
|
return 0
|
||||||
|
@ -152,6 +152,10 @@ func (g *nativeGamepadImpl) hatCount() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
axes := g.value.Get("axes")
|
axes := g.value.Get("axes")
|
||||||
if axis < 0 || axis >= axes.Length() {
|
if axis < 0 || axis >= axes.Length() {
|
||||||
|
@ -592,6 +592,10 @@ func (g *nativeGamepadImpl) hatCount() int {
|
|||||||
return g.hatCount_
|
return g.hatCount_
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
if axis < 0 || axis >= g.axisCount_ {
|
if axis < 0 || axis >= g.axisCount_ {
|
||||||
return 0
|
return 0
|
||||||
|
@ -146,6 +146,10 @@ func (g *nativeGamepadImpl) hatCount() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
func (g *nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
if axis < 0 || axis >= len(g.axisValues) {
|
if axis < 0 || axis >= len(g.axisValues) {
|
||||||
return 0
|
return 0
|
||||||
|
@ -66,6 +66,10 @@ func (*nativeGamepadImpl) hatCount() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadImpl) isAxisReady(axis int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (*nativeGamepadImpl) axisValue(axis int) float64 {
|
func (*nativeGamepadImpl) axisValue(axis int) float64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -190,6 +190,10 @@ func (n *nativeGamepadXbox) hatCount() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *nativeGamepadXbox) isAxisReady(axis int) bool {
|
||||||
|
return axis >= 0 && axis < g.axisCount()
|
||||||
|
}
|
||||||
|
|
||||||
func (n *nativeGamepadXbox) axisValue(axis int) float64 {
|
func (n *nativeGamepadXbox) axisValue(axis int) float64 {
|
||||||
switch gamepaddb.StandardAxis(axis) {
|
switch gamepaddb.StandardAxis(axis) {
|
||||||
case gamepaddb.StandardAxisLeftStickHorizontal:
|
case gamepaddb.StandardAxisLeftStickHorizontal:
|
||||||
|
@ -120,19 +120,19 @@ const (
|
|||||||
type mapping struct {
|
type mapping struct {
|
||||||
Type mappingType
|
Type mappingType
|
||||||
Index int
|
Index int
|
||||||
AxisScale int
|
AxisScale float64
|
||||||
AxisOffset int
|
AxisOffset float64
|
||||||
HatState int
|
HatState int
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gamepadNames = map[string]string{}
|
gamepadNames = map[string]string{}
|
||||||
gamepadButtonMappings = map[string]map[StandardButton]*mapping{}
|
gamepadButtonMappings = map[string]map[StandardButton]mapping{}
|
||||||
gamepadAxisMappings = map[string]map[StandardAxis]*mapping{}
|
gamepadAxisMappings = map[string]map[StandardAxis]mapping{}
|
||||||
mappingsM sync.RWMutex
|
mappingsM sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseLine(line string, platform platform) (id string, name string, buttons map[StandardButton]*mapping, axes map[StandardAxis]*mapping, err error) {
|
func parseLine(line string, platform platform) (id string, name string, buttons map[StandardButton]mapping, axes map[StandardAxis]mapping, err error) {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
return "", "", nil, nil, nil
|
return "", "", nil, nil, nil
|
||||||
@ -192,7 +192,7 @@ func parseLine(line string, platform platform) (id string, name string, buttons
|
|||||||
|
|
||||||
if b, ok := toStandardGamepadButton(tks[0]); ok {
|
if b, ok := toStandardGamepadButton(tks[0]); ok {
|
||||||
if buttons == nil {
|
if buttons == nil {
|
||||||
buttons = map[StandardButton]*mapping{}
|
buttons = map[StandardButton]mapping{}
|
||||||
}
|
}
|
||||||
buttons[b] = gb
|
buttons[b] = gb
|
||||||
continue
|
continue
|
||||||
@ -200,7 +200,7 @@ func parseLine(line string, platform platform) (id string, name string, buttons
|
|||||||
|
|
||||||
if a, ok := toStandardGamepadAxis(tks[0]); ok {
|
if a, ok := toStandardGamepadAxis(tks[0]); ok {
|
||||||
if axes == nil {
|
if axes == nil {
|
||||||
axes = map[StandardAxis]*mapping{}
|
axes = map[StandardAxis]mapping{}
|
||||||
}
|
}
|
||||||
axes[a] = gb
|
axes[a] = gb
|
||||||
continue
|
continue
|
||||||
@ -213,7 +213,7 @@ func parseLine(line string, platform platform) (id string, name string, buttons
|
|||||||
return tokens[0], tokens[1], buttons, axes, nil
|
return tokens[0], tokens[1], buttons, axes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMappingElement(str string) (*mapping, error) {
|
func parseMappingElement(str string) (mapping, error) {
|
||||||
switch {
|
switch {
|
||||||
case str[0] == 'a' || strings.HasPrefix(str, "+a") || strings.HasPrefix(str, "-a"):
|
case str[0] == 'a' || strings.HasPrefix(str, "+a") || strings.HasPrefix(str, "-a"):
|
||||||
var tilda bool
|
var tilda bool
|
||||||
@ -222,8 +222,8 @@ func parseMappingElement(str string) (*mapping, error) {
|
|||||||
tilda = true
|
tilda = true
|
||||||
}
|
}
|
||||||
|
|
||||||
min := -1
|
min := -1.0
|
||||||
max := 1
|
max := 1.0
|
||||||
numstr := str[1:]
|
numstr := str[1:]
|
||||||
|
|
||||||
if str[0] == '+' {
|
if str[0] == '+' {
|
||||||
@ -259,10 +259,10 @@ func parseMappingElement(str string) (*mapping, error) {
|
|||||||
|
|
||||||
index, err := strconv.Atoi(numstr)
|
index, err := strconv.Atoi(numstr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return mapping{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &mapping{
|
return mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: index,
|
Index: index,
|
||||||
AxisScale: scale,
|
AxisScale: scale,
|
||||||
@ -272,9 +272,9 @@ func parseMappingElement(str string) (*mapping, error) {
|
|||||||
case str[0] == 'b':
|
case str[0] == 'b':
|
||||||
index, err := strconv.Atoi(str[1:])
|
index, err := strconv.Atoi(str[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return mapping{}, err
|
||||||
}
|
}
|
||||||
return &mapping{
|
return mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: index,
|
Index: index,
|
||||||
}, nil
|
}, nil
|
||||||
@ -282,24 +282,24 @@ func parseMappingElement(str string) (*mapping, error) {
|
|||||||
case str[0] == 'h':
|
case str[0] == 'h':
|
||||||
tokens := strings.Split(str[1:], ".")
|
tokens := strings.Split(str[1:], ".")
|
||||||
if len(tokens) < 2 {
|
if len(tokens) < 2 {
|
||||||
return nil, fmt.Errorf("gamepaddb: unexpected hat: %s", str)
|
return mapping{}, fmt.Errorf("gamepaddb: unexpected hat: %s", str)
|
||||||
}
|
}
|
||||||
index, err := strconv.Atoi(tokens[0])
|
index, err := strconv.Atoi(tokens[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return mapping{}, err
|
||||||
}
|
}
|
||||||
hat, err := strconv.Atoi(tokens[1])
|
hat, err := strconv.Atoi(tokens[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return mapping{}, err
|
||||||
}
|
}
|
||||||
return &mapping{
|
return mapping{
|
||||||
Type: mappingTypeHat,
|
Type: mappingTypeHat,
|
||||||
Index: index,
|
Index: index,
|
||||||
HatState: hat,
|
HatState: hat,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("gamepaddb: unepxected mapping: %s", str)
|
return mapping{}, fmt.Errorf("gamepaddb: unepxected mapping: %s", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toStandardGamepadButton(str string) (StandardButton, bool) {
|
func toStandardGamepadButton(str string) (StandardButton, bool) {
|
||||||
@ -358,7 +358,7 @@ func toStandardGamepadAxis(str string) (StandardAxis, bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buttonMappings(id string) map[StandardButton]*mapping {
|
func buttonMappings(id string) map[StandardButton]mapping {
|
||||||
if m, ok := gamepadButtonMappings[id]; ok {
|
if m, ok := gamepadButtonMappings[id]; ok {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -370,7 +370,7 @@ func buttonMappings(id string) map[StandardButton]*mapping {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func axisMappings(id string) map[StandardAxis]*mapping {
|
func axisMappings(id string) map[StandardAxis]mapping {
|
||||||
if m, ok := gamepadAxisMappings[id]; ok {
|
if m, ok := gamepadAxisMappings[id]; ok {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -390,6 +390,7 @@ func HasStandardLayoutMapping(id string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GamepadState interface {
|
type GamepadState interface {
|
||||||
|
IsAxisReady(index int) bool
|
||||||
Axis(index int) float64
|
Axis(index int) float64
|
||||||
Button(index int) bool
|
Button(index int) bool
|
||||||
Hat(index int) int
|
Hat(index int) int
|
||||||
@ -410,10 +411,11 @@ func HasStandardAxis(id string, axis StandardAxis) bool {
|
|||||||
if mappings == nil {
|
if mappings == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return mappings[axis] != nil
|
_, ok := mappings[axis]
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func AxisValue(id string, axis StandardAxis, state GamepadState) float64 {
|
func StandardAxisValue(id string, axis StandardAxis, state GamepadState) float64 {
|
||||||
mappingsM.RLock()
|
mappingsM.RLock()
|
||||||
defer mappingsM.RUnlock()
|
defer mappingsM.RUnlock()
|
||||||
|
|
||||||
@ -422,14 +424,17 @@ func AxisValue(id string, axis StandardAxis, state GamepadState) float64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping := mappings[axis]
|
mapping, ok := mappings[axis]
|
||||||
if mapping == nil {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mapping.Type {
|
switch mapping.Type {
|
||||||
case mappingTypeAxis:
|
case mappingTypeAxis:
|
||||||
v := state.Axis(mapping.Index)*float64(mapping.AxisScale) + float64(mapping.AxisOffset)
|
if !state.IsAxisReady(mapping.Index) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
|
||||||
if v > 1 {
|
if v > 1 {
|
||||||
return 1
|
return 1
|
||||||
} else if v < -1 {
|
} else if v < -1 {
|
||||||
@ -461,30 +466,34 @@ func HasStandardButton(id string, button StandardButton) bool {
|
|||||||
if mappings == nil {
|
if mappings == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return mappings[button] != nil
|
_, ok := mappings[button]
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func ButtonValue(id string, button StandardButton, state GamepadState) float64 {
|
func StandardButtonValue(id string, button StandardButton, state GamepadState) float64 {
|
||||||
mappingsM.RLock()
|
mappingsM.RLock()
|
||||||
defer mappingsM.RUnlock()
|
defer mappingsM.RUnlock()
|
||||||
|
|
||||||
return buttonValue(id, button, state)
|
return standardButtonValue(id, button, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buttonValue(id string, button StandardButton, state GamepadState) float64 {
|
func standardButtonValue(id string, button StandardButton, state GamepadState) float64 {
|
||||||
mappings := buttonMappings(id)
|
mappings := buttonMappings(id)
|
||||||
if mappings == nil {
|
if mappings == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping := mappings[button]
|
mapping, ok := mappings[button]
|
||||||
if mapping == nil {
|
if !ok {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mapping.Type {
|
switch mapping.Type {
|
||||||
case mappingTypeAxis:
|
case mappingTypeAxis:
|
||||||
v := state.Axis(mapping.Index)*float64(mapping.AxisScale) + float64(mapping.AxisOffset)
|
if !state.IsAxisReady(mapping.Index) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
|
||||||
if v > 1 {
|
if v > 1 {
|
||||||
v = 1
|
v = 1
|
||||||
} else if v < -1 {
|
} else if v < -1 {
|
||||||
@ -513,7 +522,7 @@ func buttonValue(id string, button StandardButton, state GamepadState) float64 {
|
|||||||
// Note: should be used with >, not >=, comparisons.
|
// Note: should be used with >, not >=, comparisons.
|
||||||
const ButtonPressedThreshold = 30.0 / 255.0
|
const ButtonPressedThreshold = 30.0 / 255.0
|
||||||
|
|
||||||
func IsButtonPressed(id string, button StandardButton, state GamepadState) bool {
|
func IsStandardButtonPressed(id string, button StandardButton, state GamepadState) bool {
|
||||||
mappingsM.RLock()
|
mappingsM.RLock()
|
||||||
defer mappingsM.RUnlock()
|
defer mappingsM.RUnlock()
|
||||||
|
|
||||||
@ -522,14 +531,14 @@ func IsButtonPressed(id string, button StandardButton, state GamepadState) bool
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping := mappings[button]
|
mapping, ok := mappings[button]
|
||||||
if mapping == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mapping.Type {
|
switch mapping.Type {
|
||||||
case mappingTypeAxis:
|
case mappingTypeAxis:
|
||||||
v := buttonValue(id, button, state)
|
v := standardButtonValue(id, button, state)
|
||||||
return v > ButtonPressedThreshold
|
return v > ButtonPressedThreshold
|
||||||
case mappingTypeButton:
|
case mappingTypeButton:
|
||||||
return state.Button(mapping.Index)
|
return state.Button(mapping.Index)
|
||||||
@ -554,8 +563,8 @@ func Update(mappingData []byte) error {
|
|||||||
type parsedLine struct {
|
type parsedLine struct {
|
||||||
id string
|
id string
|
||||||
name string
|
name string
|
||||||
buttons map[StandardButton]*mapping
|
buttons map[StandardButton]mapping
|
||||||
axes map[StandardAxis]*mapping
|
axes map[StandardAxis]mapping
|
||||||
}
|
}
|
||||||
var lines []parsedLine
|
var lines []parsedLine
|
||||||
|
|
||||||
@ -609,44 +618,44 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
gamepadButtonMappings[id] = map[StandardButton]*mapping{}
|
gamepadButtonMappings[id] = map[StandardButton]mapping{}
|
||||||
gamepadAxisMappings[id] = map[StandardAxis]*mapping{}
|
gamepadAxisMappings[id] = map[StandardAxis]mapping{}
|
||||||
|
|
||||||
// For mappings, see mobile/ebitenmobileview/input_android.go.
|
// For mappings, see mobile/ebitenmobileview/input_android.go.
|
||||||
|
|
||||||
if buttonMask&(1<<SDLControllerButtonA) != 0 {
|
if buttonMask&(1<<SDLControllerButtonA) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonRightBottom] = &mapping{
|
gamepadButtonMappings[id][StandardButtonRightBottom] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonA,
|
Index: SDLControllerButtonA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonB) != 0 {
|
if buttonMask&(1<<SDLControllerButtonB) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{
|
gamepadButtonMappings[id][StandardButtonRightRight] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonB,
|
Index: SDLControllerButtonB,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use the back button as "B" for easy UI navigation with TV remotes.
|
// Use the back button as "B" for easy UI navigation with TV remotes.
|
||||||
gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{
|
gamepadButtonMappings[id][StandardButtonRightRight] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonBack,
|
Index: SDLControllerButtonBack,
|
||||||
}
|
}
|
||||||
buttonMask &^= uint16(1) << SDLControllerButtonBack
|
buttonMask &^= uint16(1) << SDLControllerButtonBack
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonX) != 0 {
|
if buttonMask&(1<<SDLControllerButtonX) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonRightLeft] = &mapping{
|
gamepadButtonMappings[id][StandardButtonRightLeft] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonX,
|
Index: SDLControllerButtonX,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonY) != 0 {
|
if buttonMask&(1<<SDLControllerButtonY) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonRightTop] = &mapping{
|
gamepadButtonMappings[id][StandardButtonRightTop] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonY,
|
Index: SDLControllerButtonY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonBack) != 0 {
|
if buttonMask&(1<<SDLControllerButtonBack) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonCenterLeft] = &mapping{
|
gamepadButtonMappings[id][StandardButtonCenterLeft] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonBack,
|
Index: SDLControllerButtonBack,
|
||||||
}
|
}
|
||||||
@ -654,69 +663,69 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
if buttonMask&(1<<SDLControllerButtonGuide) != 0 {
|
if buttonMask&(1<<SDLControllerButtonGuide) != 0 {
|
||||||
// TODO: If SDKVersion >= 30, add this code:
|
// TODO: If SDKVersion >= 30, add this code:
|
||||||
//
|
//
|
||||||
// gamepadButtonMappings[id][StandardButtonCenterCenter] = &mapping{
|
// gamepadButtonMappings[id][StandardButtonCenterCenter] = mapping{
|
||||||
// Type: mappingTypeButton,
|
// Type: mappingTypeButton,
|
||||||
// Index: SDLControllerButtonGuide,
|
// Index: SDLControllerButtonGuide,
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonStart) != 0 {
|
if buttonMask&(1<<SDLControllerButtonStart) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonCenterRight] = &mapping{
|
gamepadButtonMappings[id][StandardButtonCenterRight] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonStart,
|
Index: SDLControllerButtonStart,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 {
|
if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonLeftStick] = &mapping{
|
gamepadButtonMappings[id][StandardButtonLeftStick] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonLeftStick,
|
Index: SDLControllerButtonLeftStick,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonRightStick) != 0 {
|
if buttonMask&(1<<SDLControllerButtonRightStick) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonRightStick] = &mapping{
|
gamepadButtonMappings[id][StandardButtonRightStick] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonRightStick,
|
Index: SDLControllerButtonRightStick,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 {
|
if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonFrontTopLeft] = &mapping{
|
gamepadButtonMappings[id][StandardButtonFrontTopLeft] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonLeftShoulder,
|
Index: SDLControllerButtonLeftShoulder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 {
|
if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonFrontTopRight] = &mapping{
|
gamepadButtonMappings[id][StandardButtonFrontTopRight] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonRightShoulder,
|
Index: SDLControllerButtonRightShoulder,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 {
|
if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonLeftTop] = &mapping{
|
gamepadButtonMappings[id][StandardButtonLeftTop] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonDpadUp,
|
Index: SDLControllerButtonDpadUp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 {
|
if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonLeftBottom] = &mapping{
|
gamepadButtonMappings[id][StandardButtonLeftBottom] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonDpadDown,
|
Index: SDLControllerButtonDpadDown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 {
|
if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonLeftLeft] = &mapping{
|
gamepadButtonMappings[id][StandardButtonLeftLeft] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonDpadLeft,
|
Index: SDLControllerButtonDpadLeft,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 {
|
if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonLeftRight] = &mapping{
|
gamepadButtonMappings[id][StandardButtonLeftRight] = mapping{
|
||||||
Type: mappingTypeButton,
|
Type: mappingTypeButton,
|
||||||
Index: SDLControllerButtonDpadRight,
|
Index: SDLControllerButtonDpadRight,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if axisMask&(1<<SDLControllerAxisLeftX) != 0 {
|
if axisMask&(1<<SDLControllerAxisLeftX) != 0 {
|
||||||
gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = &mapping{
|
gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: SDLControllerAxisLeftX,
|
Index: SDLControllerAxisLeftX,
|
||||||
AxisScale: 1,
|
AxisScale: 1,
|
||||||
@ -724,7 +733,7 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if axisMask&(1<<SDLControllerAxisLeftY) != 0 {
|
if axisMask&(1<<SDLControllerAxisLeftY) != 0 {
|
||||||
gamepadAxisMappings[id][StandardAxisLeftStickVertical] = &mapping{
|
gamepadAxisMappings[id][StandardAxisLeftStickVertical] = mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: SDLControllerAxisLeftY,
|
Index: SDLControllerAxisLeftY,
|
||||||
AxisScale: 1,
|
AxisScale: 1,
|
||||||
@ -732,7 +741,7 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if axisMask&(1<<SDLControllerAxisRightX) != 0 {
|
if axisMask&(1<<SDLControllerAxisRightX) != 0 {
|
||||||
gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = &mapping{
|
gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: SDLControllerAxisRightX,
|
Index: SDLControllerAxisRightX,
|
||||||
AxisScale: 1,
|
AxisScale: 1,
|
||||||
@ -740,7 +749,7 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if axisMask&(1<<SDLControllerAxisRightY) != 0 {
|
if axisMask&(1<<SDLControllerAxisRightY) != 0 {
|
||||||
gamepadAxisMappings[id][StandardAxisRightStickVertical] = &mapping{
|
gamepadAxisMappings[id][StandardAxisRightStickVertical] = mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: SDLControllerAxisRightY,
|
Index: SDLControllerAxisRightY,
|
||||||
AxisScale: 1,
|
AxisScale: 1,
|
||||||
@ -748,7 +757,7 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 {
|
if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = &mapping{
|
gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: SDLControllerAxisTriggerLeft,
|
Index: SDLControllerAxisTriggerLeft,
|
||||||
AxisScale: 1,
|
AxisScale: 1,
|
||||||
@ -756,7 +765,7 @@ func addAndroidDefaultMappings(id string) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 {
|
if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 {
|
||||||
gamepadButtonMappings[id][StandardButtonFrontBottomRight] = &mapping{
|
gamepadButtonMappings[id][StandardButtonFrontBottomRight] = mapping{
|
||||||
Type: mappingTypeAxis,
|
Type: mappingTypeAxis,
|
||||||
Index: SDLControllerAxisTriggerRight,
|
Index: SDLControllerAxisTriggerRight,
|
||||||
AxisScale: 1,
|
AxisScale: 1,
|
||||||
|
@ -73,6 +73,10 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
stmts = append(stmts, ss...)
|
stmts = append(stmts, ss...)
|
||||||
|
if len(ts) == 0 {
|
||||||
|
cs.addError(e.Pos(), fmt.Sprintf("unexpected binary operator: %s", e.X))
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
lhst := ts[0]
|
lhst := ts[0]
|
||||||
|
|
||||||
rhs, ts, ss, ok := cs.parseExpr(block, fname, e.Y, markLocalVariableUsed)
|
rhs, ts, ss, ok := cs.parseExpr(block, fname, e.Y, markLocalVariableUsed)
|
||||||
@ -283,26 +287,26 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process the expression as a regular function call.
|
// Process the expression as a regular function call.
|
||||||
var t shaderir.Type
|
var finalType shaderir.Type
|
||||||
switch callee.BuiltinFunc {
|
switch callee.BuiltinFunc {
|
||||||
case shaderir.BoolF:
|
case shaderir.BoolF:
|
||||||
if err := checkArgsForBoolBuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForBoolBuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Bool}
|
finalType = shaderir.Type{Main: shaderir.Bool}
|
||||||
case shaderir.IntF:
|
case shaderir.IntF:
|
||||||
if err := checkArgsForIntBuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForIntBuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Int}
|
finalType = shaderir.Type{Main: shaderir.Int}
|
||||||
case shaderir.FloatF:
|
case shaderir.FloatF:
|
||||||
if err := checkArgsForFloatBuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForFloatBuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Float}
|
finalType = shaderir.Type{Main: shaderir.Float}
|
||||||
case shaderir.Vec2F:
|
case shaderir.Vec2F:
|
||||||
if err := checkArgsForVec2BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForVec2BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
@ -315,7 +319,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
args[i].Const = gconstant.ToFloat(args[i].Const)
|
args[i].Const = gconstant.ToFloat(args[i].Const)
|
||||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Vec2}
|
finalType = shaderir.Type{Main: shaderir.Vec2}
|
||||||
case shaderir.Vec3F:
|
case shaderir.Vec3F:
|
||||||
if err := checkArgsForVec3BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForVec3BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
@ -328,7 +332,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
args[i].Const = gconstant.ToFloat(args[i].Const)
|
args[i].Const = gconstant.ToFloat(args[i].Const)
|
||||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Vec3}
|
finalType = shaderir.Type{Main: shaderir.Vec3}
|
||||||
case shaderir.Vec4F:
|
case shaderir.Vec4F:
|
||||||
if err := checkArgsForVec4BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForVec4BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
@ -341,25 +345,25 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
args[i].Const = gconstant.ToFloat(args[i].Const)
|
args[i].Const = gconstant.ToFloat(args[i].Const)
|
||||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Vec4}
|
finalType = shaderir.Type{Main: shaderir.Vec4}
|
||||||
case shaderir.IVec2F:
|
case shaderir.IVec2F:
|
||||||
if err := checkArgsForIVec2BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForIVec2BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.IVec2}
|
finalType = shaderir.Type{Main: shaderir.IVec2}
|
||||||
case shaderir.IVec3F:
|
case shaderir.IVec3F:
|
||||||
if err := checkArgsForIVec3BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForIVec3BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.IVec3}
|
finalType = shaderir.Type{Main: shaderir.IVec3}
|
||||||
case shaderir.IVec4F:
|
case shaderir.IVec4F:
|
||||||
if err := checkArgsForIVec4BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForIVec4BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.IVec4}
|
finalType = shaderir.Type{Main: shaderir.IVec4}
|
||||||
case shaderir.Mat2F:
|
case shaderir.Mat2F:
|
||||||
if err := checkArgsForMat2BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForMat2BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
@ -372,7 +376,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
args[i].Const = gconstant.ToFloat(args[i].Const)
|
args[i].Const = gconstant.ToFloat(args[i].Const)
|
||||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Mat2}
|
finalType = shaderir.Type{Main: shaderir.Mat2}
|
||||||
case shaderir.Mat3F:
|
case shaderir.Mat3F:
|
||||||
if err := checkArgsForMat3BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForMat3BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
@ -385,7 +389,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
args[i].Const = gconstant.ToFloat(args[i].Const)
|
args[i].Const = gconstant.ToFloat(args[i].Const)
|
||||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Mat3}
|
finalType = shaderir.Type{Main: shaderir.Mat3}
|
||||||
case shaderir.Mat4F:
|
case shaderir.Mat4F:
|
||||||
if err := checkArgsForMat4BuiltinFunc(args, argts); err != nil {
|
if err := checkArgsForMat4BuiltinFunc(args, argts); err != nil {
|
||||||
cs.addError(e.Pos(), err.Error())
|
cs.addError(e.Pos(), err.Error())
|
||||||
@ -398,7 +402,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
args[i].Const = gconstant.ToFloat(args[i].Const)
|
args[i].Const = gconstant.ToFloat(args[i].Const)
|
||||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Mat4}
|
finalType = shaderir.Type{Main: shaderir.Mat4}
|
||||||
case shaderir.TexelAt:
|
case shaderir.TexelAt:
|
||||||
if len(args) != 2 {
|
if len(args) != 2 {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("number of %s's arguments must be 2 but %d", callee.BuiltinFunc, len(args)))
|
cs.addError(e.Pos(), fmt.Sprintf("number of %s's arguments must be 2 but %d", callee.BuiltinFunc, len(args)))
|
||||||
@ -412,7 +416,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
cs.addError(e.Pos(), fmt.Sprintf("cannot use %s as vec2 value in argument to %s", argts[1].String(), callee.BuiltinFunc))
|
cs.addError(e.Pos(), fmt.Sprintf("cannot use %s as vec2 value in argument to %s", argts[1].String(), callee.BuiltinFunc))
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
t = shaderir.Type{Main: shaderir.Vec4}
|
finalType = shaderir.Type{Main: shaderir.Vec4}
|
||||||
case shaderir.DiscardF:
|
case shaderir.DiscardF:
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("number of %s's arguments must be 0 but %d", callee.BuiltinFunc, len(args)))
|
cs.addError(e.Pos(), fmt.Sprintf("number of %s's arguments must be 0 but %d", callee.BuiltinFunc, len(args)))
|
||||||
@ -435,13 +439,17 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
}
|
}
|
||||||
switch callee.BuiltinFunc {
|
switch callee.BuiltinFunc {
|
||||||
case shaderir.Clamp:
|
case shaderir.Clamp:
|
||||||
if kind, allConsts := resolveConstKind(args, argts); allConsts {
|
if kind, _ := resolveConstKind(args, argts); kind != gconstant.Unknown {
|
||||||
switch kind {
|
switch kind {
|
||||||
case gconstant.Unknown:
|
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("%s's arguments don't match: %s, %s, and %s", callee.BuiltinFunc, argts[0].String(), argts[1].String(), argts[2].String()))
|
|
||||||
return nil, nil, nil, false
|
|
||||||
case gconstant.Int:
|
case gconstant.Int:
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
if arg.Const == nil {
|
||||||
|
if argts[i].Main != shaderir.Int {
|
||||||
|
cs.addError(e.Pos(), fmt.Sprintf("%s's arguments don't match: %s, %s, and %s", callee.BuiltinFunc, argts[0].String(), argts[1].String(), argts[2].String()))
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
v := gconstant.ToInt(arg.Const)
|
v := gconstant.ToInt(arg.Const)
|
||||||
if v.Kind() == gconstant.Unknown {
|
if v.Kind() == gconstant.Unknown {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type int", arg.Const.String()))
|
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type int", arg.Const.String()))
|
||||||
@ -452,6 +460,13 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
}
|
}
|
||||||
case gconstant.Float:
|
case gconstant.Float:
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
if arg.Const == nil {
|
||||||
|
if argts[i].Main != shaderir.Float {
|
||||||
|
cs.addError(e.Pos(), fmt.Sprintf("%s's arguments don't match: %s, %s, and %s", callee.BuiltinFunc, argts[0].String(), argts[1].String(), argts[2].String()))
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
v := gconstant.ToFloat(arg.Const)
|
v := gconstant.ToFloat(arg.Const)
|
||||||
if v.Kind() == gconstant.Unknown {
|
if v.Kind() == gconstant.Unknown {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type float", arg.Const.String()))
|
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type float", arg.Const.String()))
|
||||||
@ -551,9 +566,9 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
|
|
||||||
switch callee.BuiltinFunc {
|
switch callee.BuiltinFunc {
|
||||||
case shaderir.Smoothstep:
|
case shaderir.Smoothstep:
|
||||||
t = argts[2]
|
finalType = argts[2]
|
||||||
default:
|
default:
|
||||||
t = argts[0]
|
finalType = argts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
case shaderir.Atan2, shaderir.Pow, shaderir.Mod, shaderir.Min, shaderir.Max, shaderir.Step, shaderir.Distance, shaderir.Dot, shaderir.Cross, shaderir.Reflect:
|
case shaderir.Atan2, shaderir.Pow, shaderir.Mod, shaderir.Min, shaderir.Max, shaderir.Step, shaderir.Distance, shaderir.Dot, shaderir.Cross, shaderir.Reflect:
|
||||||
@ -565,13 +580,17 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
|
|
||||||
switch callee.BuiltinFunc {
|
switch callee.BuiltinFunc {
|
||||||
case shaderir.Min, shaderir.Max:
|
case shaderir.Min, shaderir.Max:
|
||||||
if kind, allConsts := resolveConstKind(args, argts); allConsts {
|
if kind, _ := resolveConstKind(args, argts); kind != gconstant.Unknown {
|
||||||
switch kind {
|
switch kind {
|
||||||
case gconstant.Unknown:
|
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("%s's arguments don't match: %s and %s", callee.BuiltinFunc, argts[0].String(), argts[1].String()))
|
|
||||||
return nil, nil, nil, false
|
|
||||||
case gconstant.Int:
|
case gconstant.Int:
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
if arg.Const == nil {
|
||||||
|
if argts[i].Main != shaderir.Int {
|
||||||
|
cs.addError(e.Pos(), fmt.Sprintf("%s's arguments don't match: %s and %s", callee.BuiltinFunc, argts[0].String(), argts[1].String()))
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
v := gconstant.ToInt(arg.Const)
|
v := gconstant.ToInt(arg.Const)
|
||||||
if v.Kind() == gconstant.Unknown {
|
if v.Kind() == gconstant.Unknown {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type int", arg.Const.String()))
|
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type int", arg.Const.String()))
|
||||||
@ -582,6 +601,13 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
}
|
}
|
||||||
case gconstant.Float:
|
case gconstant.Float:
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
if arg.Const == nil {
|
||||||
|
if argts[i].Main != shaderir.Float {
|
||||||
|
cs.addError(e.Pos(), fmt.Sprintf("%s's arguments don't match: %s and %s", callee.BuiltinFunc, argts[0].String(), argts[1].String()))
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
v := gconstant.ToFloat(arg.Const)
|
v := gconstant.ToFloat(arg.Const)
|
||||||
if v.Kind() == gconstant.Unknown {
|
if v.Kind() == gconstant.Unknown {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type float", arg.Const.String()))
|
cs.addError(e.Pos(), fmt.Sprintf("cannot convert %s to type float", arg.Const.String()))
|
||||||
@ -669,11 +695,11 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
}
|
}
|
||||||
switch callee.BuiltinFunc {
|
switch callee.BuiltinFunc {
|
||||||
case shaderir.Distance, shaderir.Dot:
|
case shaderir.Distance, shaderir.Dot:
|
||||||
t = shaderir.Type{Main: shaderir.Float}
|
finalType = shaderir.Type{Main: shaderir.Float}
|
||||||
case shaderir.Step:
|
case shaderir.Step:
|
||||||
t = argts[1]
|
finalType = argts[1]
|
||||||
default:
|
default:
|
||||||
t = argts[0]
|
finalType = argts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -718,9 +744,9 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if callee.BuiltinFunc == shaderir.Length {
|
if callee.BuiltinFunc == shaderir.Length {
|
||||||
t = shaderir.Type{Main: shaderir.Float}
|
finalType = shaderir.Type{Main: shaderir.Float}
|
||||||
} else {
|
} else {
|
||||||
t = argts[0]
|
finalType = argts[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return []shaderir.Expr{
|
return []shaderir.Expr{
|
||||||
@ -728,7 +754,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
Type: shaderir.Call,
|
Type: shaderir.Call,
|
||||||
Exprs: append([]shaderir.Expr{callee}, args...),
|
Exprs: append([]shaderir.Expr{callee}, args...),
|
||||||
},
|
},
|
||||||
}, []shaderir.Type{t}, stmts, true
|
}, []shaderir.Type{finalType}, stmts, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee.Type != shaderir.FunctionExpr {
|
if callee.Type != shaderir.FunctionExpr {
|
||||||
@ -906,7 +932,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isValidSwizzling(e.Sel.Name, types[0]) {
|
if len(types) == 0 || !isValidSwizzling(e.Sel.Name, types[0]) {
|
||||||
cs.addError(e.Pos(), fmt.Sprintf("unexpected swizzling: %s", e.Sel.Name))
|
cs.addError(e.Pos(), fmt.Sprintf("unexpected swizzling: %s", e.Sel.Name))
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
@ -962,6 +988,10 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a unary operator: %s", e.X))
|
cs.addError(e.Pos(), fmt.Sprintf("multiple-value context is not available at a unary operator: %s", e.X))
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
|
if len(ts) == 0 {
|
||||||
|
cs.addError(e.Pos(), fmt.Sprintf("unexpected unary operator: %s", e.X))
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
if exprs[0].Const != nil {
|
if exprs[0].Const != nil {
|
||||||
v := gconstant.UnaryOp(e.Op, exprs[0].Const, 0)
|
v := gconstant.UnaryOp(e.Op, exprs[0].Const, 0)
|
||||||
@ -1109,6 +1139,10 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
|||||||
cs.addError(e.Pos(), "multiple-value context is not available at an index expression")
|
cs.addError(e.Pos(), "multiple-value context is not available at an index expression")
|
||||||
return nil, nil, nil, false
|
return nil, nil, nil, false
|
||||||
}
|
}
|
||||||
|
if len(ts) == 0 {
|
||||||
|
cs.addError(e.Pos(), "unexpected index expression")
|
||||||
|
return nil, nil, nil, false
|
||||||
|
}
|
||||||
x := exprs[0]
|
x := exprs[0]
|
||||||
t := ts[0]
|
t := ts[0]
|
||||||
|
|
||||||
@ -1169,8 +1203,24 @@ func resolveConstKind(exprs []shaderir.Expr, ts []shaderir.Type) (kind gconstant
|
|||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allConsts = true
|
||||||
for _, expr := range exprs {
|
for _, expr := range exprs {
|
||||||
if expr.Const == nil {
|
if expr.Const == nil {
|
||||||
|
allConsts = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allConsts {
|
||||||
|
for _, t := range ts {
|
||||||
|
if t.Main == shaderir.None {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.Main == shaderir.Float {
|
||||||
|
return gconstant.Float, false
|
||||||
|
}
|
||||||
|
if t.Main == shaderir.Int {
|
||||||
|
return gconstant.Int, false
|
||||||
|
}
|
||||||
return gconstant.Unknown, false
|
return gconstant.Unknown, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1199,13 +1249,15 @@ func resolveConstKind(exprs []shaderir.Expr, ts []shaderir.Type) (kind gconstant
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if kind != gconstant.Unknown {
|
||||||
|
return kind, true
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer floats over integers for non-typed constant values.
|
// Prefer floats over integers for non-typed constant values.
|
||||||
// For example, max(1.0, 1) should return a float value.
|
// For example, max(1.0, 1) should return a float value.
|
||||||
if kind == gconstant.Unknown {
|
for _, expr := range exprs {
|
||||||
for _, expr := range exprs {
|
if expr.Const.Kind() == gconstant.Float {
|
||||||
if expr.Const.Kind() == gconstant.Float {
|
return gconstant.Float, true
|
||||||
return gconstant.Float, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -740,7 +740,9 @@ func (cs *compileState) parseFuncParams(block *block, fname string, d *ast.FuncD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(out) == 1 && out[0].name == "" {
|
// If there is only one returning value, it is treated as a returning value.
|
||||||
|
// An array cannot be a returning value, especially for HLSL (#2923).
|
||||||
|
if len(out) == 1 && out[0].name == "" && out[0].typ.Main != shaderir.Array {
|
||||||
ret = out[0].typ
|
ret = out[0].typ
|
||||||
out = nil
|
out = nil
|
||||||
}
|
}
|
||||||
|
@ -2345,6 +2345,18 @@ func TestSyntaxBuiltinFuncDoubleArgsType(t *testing.T) {
|
|||||||
{stmt: "a := {{.Func}}(1, 1); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1, 1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1.0, 1); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1.0, 1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1, 1.0); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1, 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), 1); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), 1.0); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), 1.1); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1.1); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1.0, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1.1, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1.0, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1.1, float(1)); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(int(1), int(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(int(1), int(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, vec2(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec2(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, vec3(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec3(1)); _ = a", err: true},
|
||||||
@ -2405,6 +2417,18 @@ func TestSyntaxBuiltinFuncDoubleArgsType2(t *testing.T) {
|
|||||||
{stmt: "a := {{.Func}}(1, 1); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1, 1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1.0, 1); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1.0, 1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1, 1.0); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1, 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), 1); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), 1.0); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), 1.1); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1.1); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1.0, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1.1, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1.0, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1.1, float(1)); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(int(1), int(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(int(1), int(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, vec2(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec2(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, vec3(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec3(1)); _ = a", err: true},
|
||||||
@ -2465,13 +2489,25 @@ func TestSyntaxBuiltinFuncArgsMinMax(t *testing.T) {
|
|||||||
{stmt: "a := {{.Func}}(1, 1.0); var _ float = a", err: false},
|
{stmt: "a := {{.Func}}(1, 1.0); var _ float = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1.1, 1); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1.1, 1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1, 1.1); _ = a", err: false},
|
{stmt: "a := {{.Func}}(1, 1.1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(int(1), int(1)); var _ int = a", err: false},
|
{stmt: "a := {{.Func}}(int(1), 1); var _ int = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(int(1), 1.0); var _ int = a", err: false},
|
{stmt: "a := {{.Func}}(int(1), 1.0); var _ int = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(int(1), 1.1); _ = a", err: true},
|
{stmt: "a := {{.Func}}(int(1), 1.1); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(float(1), 1.1); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1, int(1)); var _ int = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1.0, int(1)); var _ int = a", err: false},
|
{stmt: "a := {{.Func}}(1.0, int(1)); var _ int = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1.1, int(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1.1, int(1)); _ = a", err: true},
|
||||||
|
{stmt: "a := {{.Func}}(1, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1.0, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(1.1, float(1)); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(int(1), int(1)); var _ int = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(int(1), float(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(int(1), float(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(float(1), int(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(float(1), int(1)); _ = a", err: true},
|
||||||
|
{stmt: "x := 1.1; a := {{.Func}}(int(x), 1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1; a := {{.Func}}(float(x), 1.1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1.1; a := {{.Func}}(1, int(x)); _ = a", err: false},
|
||||||
|
{stmt: "x := 1; a := {{.Func}}(1.1, float(x)); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(1, vec2(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec2(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, vec3(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec3(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, vec4(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, vec4(1)); _ = a", err: true},
|
||||||
@ -2479,14 +2515,20 @@ func TestSyntaxBuiltinFuncArgsMinMax(t *testing.T) {
|
|||||||
{stmt: "a := {{.Func}}(1, ivec3(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, ivec3(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(1, ivec4(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(1, ivec4(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec2(1), 1); _ = a", err: false}, // The second argument can be a scalar.
|
{stmt: "a := {{.Func}}(vec2(1), 1); _ = a", err: false}, // The second argument can be a scalar.
|
||||||
|
{stmt: "a := {{.Func}}(vec2(1), 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(vec2(1), 1.1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(vec2(1), vec2(1)); _ = a", err: false},
|
{stmt: "a := {{.Func}}(vec2(1), vec2(1)); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(vec2(1), vec3(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(vec2(1), vec3(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec2(1), vec4(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(vec2(1), vec4(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec3(1), 1); _ = a", err: false}, // The second argument can be a scalar.
|
{stmt: "a := {{.Func}}(vec3(1), 1); _ = a", err: false}, // The second argument can be a scalar.
|
||||||
|
{stmt: "a := {{.Func}}(vec3(1), 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(vec3(1), 1.1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(vec3(1), vec2(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(vec3(1), vec2(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec3(1), vec3(1)); _ = a", err: false},
|
{stmt: "a := {{.Func}}(vec3(1), vec3(1)); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(vec3(1), vec4(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(vec3(1), vec4(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec4(1), 1); _ = a", err: false}, // The second argument can be a scalar.
|
{stmt: "a := {{.Func}}(vec4(1), 1); _ = a", err: false}, // The second argument can be a scalar.
|
||||||
|
{stmt: "a := {{.Func}}(vec4(1), 1.0); _ = a", err: false},
|
||||||
|
{stmt: "a := {{.Func}}(vec4(1), 1.1); _ = a", err: false},
|
||||||
{stmt: "a := {{.Func}}(vec4(1), vec2(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(vec4(1), vec2(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec4(1), vec3(1)); _ = a", err: true},
|
{stmt: "a := {{.Func}}(vec4(1), vec3(1)); _ = a", err: true},
|
||||||
{stmt: "a := {{.Func}}(vec4(1), vec4(1)); _ = a", err: false},
|
{stmt: "a := {{.Func}}(vec4(1), vec4(1)); _ = a", err: false},
|
||||||
@ -2661,6 +2703,17 @@ func TestSyntaxBuiltinFuncClampType(t *testing.T) {
|
|||||||
{stmt: "a := clamp(int(1), 1, 1.0); var _ int = a", err: false},
|
{stmt: "a := clamp(int(1), 1, 1.0); var _ int = a", err: false},
|
||||||
{stmt: "a := clamp(int(1), 1.1, 1); _ = a", err: true},
|
{stmt: "a := clamp(int(1), 1.1, 1); _ = a", err: true},
|
||||||
{stmt: "a := clamp(int(1), 1, 1.1); _ = a", err: true},
|
{stmt: "a := clamp(int(1), 1, 1.1); _ = a", err: true},
|
||||||
|
{stmt: "a := clamp(float(1), 1, 1); var _ float = a", err: false},
|
||||||
|
{stmt: "a := clamp(float(1), 1.0, 1); var _ float = a", err: false},
|
||||||
|
{stmt: "a := clamp(float(1), 1, 1.0); var _ float = a", err: false},
|
||||||
|
{stmt: "a := clamp(float(1), 1.1, 1); _ = a", err: false},
|
||||||
|
{stmt: "a := clamp(float(1), 1, 1.1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1.1; a := clamp(int(x), 1, 1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1; a := clamp(float(x), 1.1, 1.1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1.1; a := clamp(1, int(x), 1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1; a := clamp(1.1, float(x), 1.1); _ = a", err: false},
|
||||||
|
{stmt: "x := 1.1; a := clamp(1, 1, int(x)); _ = a", err: false},
|
||||||
|
{stmt: "x := 1; a := clamp(1.1, 1.1, float(x)); _ = a", err: false},
|
||||||
{stmt: "a := clamp(1.0, 1, 1); var _ float = a", err: false},
|
{stmt: "a := clamp(1.0, 1, 1); var _ float = a", err: false},
|
||||||
{stmt: "a := clamp(1, 1.0, 1); var _ float = a", err: false},
|
{stmt: "a := clamp(1, 1.0, 1); var _ float = a", err: false},
|
||||||
{stmt: "a := clamp(1, 1, 1.0); var _ float = a", err: false},
|
{stmt: "a := clamp(1, 1, 1.0); var _ float = a", err: false},
|
||||||
@ -4165,3 +4218,51 @@ func Bar() (int, int) {
|
|||||||
t.Error("compileToIR must return an error but did not")
|
t.Error("compileToIR must return an error but did not")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue #2926
|
||||||
|
func TestSyntaxNonTypeExpression(t *testing.T) {
|
||||||
|
if _, err := compileToIR([]byte(`package main
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bar() float {
|
||||||
|
return +Foo
|
||||||
|
}
|
||||||
|
`)); err == nil {
|
||||||
|
t.Error("compileToIR must return an error but did not")
|
||||||
|
}
|
||||||
|
if _, err := compileToIR([]byte(`package main
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bar() float {
|
||||||
|
return Foo + 1.0
|
||||||
|
}
|
||||||
|
`)); err == nil {
|
||||||
|
t.Error("compileToIR must return an error but did not")
|
||||||
|
}
|
||||||
|
if _, err := compileToIR([]byte(`package main
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bar() float {
|
||||||
|
return Foo.x
|
||||||
|
}
|
||||||
|
`)); err == nil {
|
||||||
|
t.Error("compileToIR must return an error but did not")
|
||||||
|
}
|
||||||
|
if _, err := compileToIR([]byte(`package main
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bar() float {
|
||||||
|
return Foo[0]
|
||||||
|
}
|
||||||
|
`)); err == nil {
|
||||||
|
t.Error("compileToIR must return an error but did not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
40
internal/shader/testdata/array.expected.vs
vendored
40
internal/shader/testdata/array.expected.vs
vendored
@ -1,25 +1,29 @@
|
|||||||
uniform vec2 U0[4];
|
uniform vec2 U0[4];
|
||||||
|
|
||||||
vec2[2] F0(void);
|
void F0(out vec2 l0[2]);
|
||||||
vec2[2] F1(void);
|
void F1(out vec2 l0[2]);
|
||||||
|
|
||||||
vec2[2] F0(void) {
|
void F0(out vec2 l0[2]) {
|
||||||
vec2 l0[2];
|
|
||||||
l0[0] = vec2(0);
|
|
||||||
l0[1] = vec2(0);
|
|
||||||
return l0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2[2] F1(void) {
|
|
||||||
vec2 l0[2];
|
|
||||||
l0[0] = vec2(0);
|
|
||||||
l0[1] = vec2(0);
|
|
||||||
vec2 l1[2];
|
vec2 l1[2];
|
||||||
l1[0] = vec2(0);
|
l1[0] = vec2(0);
|
||||||
l1[1] = vec2(0);
|
l1[1] = vec2(0);
|
||||||
(l0)[0] = vec2(1.0);
|
l0[0] = l1[0];
|
||||||
l1[0] = l0[0];
|
l0[1] = l1[1];
|
||||||
l1[1] = l0[1];
|
return;
|
||||||
(l1)[1] = vec2(2.0);
|
}
|
||||||
return l1;
|
|
||||||
|
void F1(out vec2 l0[2]) {
|
||||||
|
vec2 l1[2];
|
||||||
|
l1[0] = vec2(0);
|
||||||
|
l1[1] = vec2(0);
|
||||||
|
vec2 l2[2];
|
||||||
|
l2[0] = vec2(0);
|
||||||
|
l2[1] = vec2(0);
|
||||||
|
(l1)[0] = vec2(1.0);
|
||||||
|
l2[0] = l1[0];
|
||||||
|
l2[1] = l1[1];
|
||||||
|
(l2)[1] = vec2(2.0);
|
||||||
|
l0[0] = l2[0];
|
||||||
|
l0[1] = l2[1];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
15
internal/shader/testdata/array2.expected.metal
vendored
15
internal/shader/testdata/array2.expected.metal
vendored
@ -1,11 +1,12 @@
|
|||||||
array<float2, 3> F0(void);
|
void F0(thread array<float2, 3>& l0);
|
||||||
|
|
||||||
array<float2, 3> F0(void) {
|
void F0(thread array<float2, 3>& l0) {
|
||||||
array<float2, 2> l0 = {};
|
array<float2, 2> l1 = {};
|
||||||
array<float2, 3> l1 = {};
|
array<float2, 3> l2 = {};
|
||||||
{
|
{
|
||||||
array<float2, 2> l1 = {};
|
array<float2, 2> l2 = {};
|
||||||
l1 = l0;
|
l2 = l1;
|
||||||
}
|
}
|
||||||
return l1;
|
l0 = l2;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
29
internal/shader/testdata/array2.expected.vs
vendored
29
internal/shader/testdata/array2.expected.vs
vendored
@ -1,19 +1,22 @@
|
|||||||
vec2[3] F0(void);
|
void F0(out vec2 l0[3]);
|
||||||
|
|
||||||
vec2[3] F0(void) {
|
void F0(out vec2 l0[3]) {
|
||||||
vec2 l0[2];
|
vec2 l1[2];
|
||||||
l0[0] = vec2(0);
|
|
||||||
l0[1] = vec2(0);
|
|
||||||
vec2 l1[3];
|
|
||||||
l1[0] = vec2(0);
|
l1[0] = vec2(0);
|
||||||
l1[1] = vec2(0);
|
l1[1] = vec2(0);
|
||||||
l1[2] = vec2(0);
|
vec2 l2[3];
|
||||||
|
l2[0] = vec2(0);
|
||||||
|
l2[1] = vec2(0);
|
||||||
|
l2[2] = vec2(0);
|
||||||
{
|
{
|
||||||
vec2 l1[2];
|
vec2 l2[2];
|
||||||
l1[0] = vec2(0);
|
l2[0] = vec2(0);
|
||||||
l1[1] = vec2(0);
|
l2[1] = vec2(0);
|
||||||
l1[0] = l0[0];
|
l2[0] = l1[0];
|
||||||
l1[1] = l0[1];
|
l2[1] = l1[1];
|
||||||
}
|
}
|
||||||
return l1;
|
l0[0] = l2[0];
|
||||||
|
l0[1] = l2[1];
|
||||||
|
l0[2] = l2[2];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
26
internal/shader/testdata/issue2840.expected.vs
vendored
26
internal/shader/testdata/issue2840.expected.vs
vendored
@ -1,16 +1,18 @@
|
|||||||
float[1] F0(void);
|
void F0(out float l0[1]);
|
||||||
int[1] F1(void);
|
void F1(out int l0[1]);
|
||||||
|
|
||||||
float[1] F0(void) {
|
void F0(out float l0[1]) {
|
||||||
float l0[1];
|
float l1[1];
|
||||||
l0[0] = float(0);
|
l1[0] = float(0);
|
||||||
(l0)[0] = 1.0;
|
(l1)[0] = 1.0;
|
||||||
return l0;
|
l0[0] = l1[0];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int[1] F1(void) {
|
void F1(out int l0[1]) {
|
||||||
int l0[1];
|
int l1[1];
|
||||||
l0[0] = 0;
|
l1[0] = 0;
|
||||||
(l0)[0] = 1;
|
(l1)[0] = 1;
|
||||||
return l0;
|
l0[0] = l1[0];
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
@ -285,6 +285,10 @@ func (c *context) screenScaleAndOffsets() (scale, offsetX, offsetY float64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) LogicalPositionToClientPosition(x, y float64) (float64, float64) {
|
func (u *UserInterface) LogicalPositionToClientPositionInNativePixels(x, y float64) (float64, float64) {
|
||||||
return u.context.logicalPositionToClientPosition(x, y, u.Monitor().DeviceScaleFactor())
|
s := u.Monitor().DeviceScaleFactor()
|
||||||
|
x, y = u.context.logicalPositionToClientPosition(x, y, s)
|
||||||
|
x = dipToNativePixels(x, s)
|
||||||
|
y = dipToNativePixels(y, s)
|
||||||
|
return x, y
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,8 @@ func (u *UserInterface) updateInputStateImpl() error {
|
|||||||
|
|
||||||
if !math.IsNaN(cx) && !math.IsNaN(cy) {
|
if !math.IsNaN(cx) && !math.IsNaN(cy) {
|
||||||
cx2, cy2 := u.context.logicalPositionToClientPosition(cx, cy, s)
|
cx2, cy2 := u.context.logicalPositionToClientPosition(cx, cy, s)
|
||||||
cx2 = dipToGLFWPixel(cx2, m)
|
cx2 = dipToGLFWPixel(cx2, s)
|
||||||
cy2 = dipToGLFWPixel(cy2, m)
|
cy2 = dipToGLFWPixel(cy2, s)
|
||||||
if err := u.window.SetCursorPos(cx2, cy2); err != nil {
|
if err := u.window.SetCursorPos(cx2, cy2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -106,8 +106,8 @@ func (u *UserInterface) updateInputStateImpl() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cx2 = dipFromGLFWPixel(cx2, m)
|
cx2 = dipFromGLFWPixel(cx2, s)
|
||||||
cy2 = dipFromGLFWPixel(cy2, m)
|
cy2 = dipFromGLFWPixel(cy2, s)
|
||||||
cx, cy = u.context.clientPositionToLogicalPosition(cx2, cy2, s)
|
cx, cy = u.context.clientPositionToLogicalPosition(cx2, cy2, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,7 @@ const (
|
|||||||
KeyF24
|
KeyF24
|
||||||
KeyHome
|
KeyHome
|
||||||
KeyInsert
|
KeyInsert
|
||||||
|
KeyIntlBackslash
|
||||||
KeyMetaLeft
|
KeyMetaLeft
|
||||||
KeyMetaRight
|
KeyMetaRight
|
||||||
KeyMinus
|
KeyMinus
|
||||||
@ -315,6 +316,8 @@ func (k Key) String() string {
|
|||||||
return "KeyHome"
|
return "KeyHome"
|
||||||
case KeyInsert:
|
case KeyInsert:
|
||||||
return "KeyInsert"
|
return "KeyInsert"
|
||||||
|
case KeyIntlBackslash:
|
||||||
|
return "KeyIntlBackslash"
|
||||||
case KeyMetaLeft:
|
case KeyMetaLeft:
|
||||||
return "KeyMetaLeft"
|
return "KeyMetaLeft"
|
||||||
case KeyMetaRight:
|
case KeyMetaRight:
|
||||||
|
@ -89,6 +89,7 @@ var uiKeyToGLFWKey = map[Key]glfw.Key{
|
|||||||
KeyHome: glfw.KeyHome,
|
KeyHome: glfw.KeyHome,
|
||||||
KeyI: glfw.KeyI,
|
KeyI: glfw.KeyI,
|
||||||
KeyInsert: glfw.KeyInsert,
|
KeyInsert: glfw.KeyInsert,
|
||||||
|
KeyIntlBackslash: glfw.KeyWorld1,
|
||||||
KeyJ: glfw.KeyJ,
|
KeyJ: glfw.KeyJ,
|
||||||
KeyK: glfw.KeyK,
|
KeyK: glfw.KeyK,
|
||||||
KeyL: glfw.KeyL,
|
KeyL: glfw.KeyL,
|
||||||
|
@ -87,6 +87,7 @@ var uiKeyToJSCode = map[Key]js.Value{
|
|||||||
KeyHome: js.ValueOf("Home"),
|
KeyHome: js.ValueOf("Home"),
|
||||||
KeyI: js.ValueOf("KeyI"),
|
KeyI: js.ValueOf("KeyI"),
|
||||||
KeyInsert: js.ValueOf("Insert"),
|
KeyInsert: js.ValueOf("Insert"),
|
||||||
|
KeyIntlBackslash: js.ValueOf("IntlBackslash"),
|
||||||
KeyJ: js.ValueOf("KeyJ"),
|
KeyJ: js.ValueOf("KeyJ"),
|
||||||
KeyK: js.ValueOf("KeyK"),
|
KeyK: js.ValueOf("KeyK"),
|
||||||
KeyL: js.ValueOf("KeyL"),
|
KeyL: js.ValueOf("KeyL"),
|
||||||
|
@ -52,7 +52,8 @@ func (m *Monitor) DeviceScaleFactor() float64 {
|
|||||||
|
|
||||||
func (m *Monitor) sizeInDIP() (float64, float64) {
|
func (m *Monitor) sizeInDIP() (float64, float64) {
|
||||||
w, h := m.boundsInGLFWPixels.Dx(), m.boundsInGLFWPixels.Dy()
|
w, h := m.boundsInGLFWPixels.Dx(), m.boundsInGLFWPixels.Dy()
|
||||||
return dipFromGLFWPixel(float64(w), m), dipFromGLFWPixel(float64(h), m)
|
s := m.DeviceScaleFactor()
|
||||||
|
return dipFromGLFWPixel(float64(w), s), dipFromGLFWPixel(float64(h), s)
|
||||||
}
|
}
|
||||||
|
|
||||||
type monitors struct {
|
type monitors struct {
|
||||||
|
@ -129,3 +129,7 @@ func deviceScaleFactorImpl() float64 {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x * scale
|
||||||
|
}
|
||||||
|
@ -206,7 +206,7 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
|
|||||||
return vm.Width, vm.Height, nil
|
return vm.Width, vm.Height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
func dipFromGLFWPixel(x float64, scale float64) float64 {
|
||||||
// NOTE: On macOS, GLFW exposes the device independent coordinate system.
|
// NOTE: On macOS, GLFW exposes the device independent coordinate system.
|
||||||
// Thus, the conversion functions are unnecessary,
|
// Thus, the conversion functions are unnecessary,
|
||||||
// however we still need the deviceScaleFactor internally
|
// however we still need the deviceScaleFactor internally
|
||||||
@ -214,7 +214,7 @@ func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
|||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
|
func dipToGLFWPixel(x float64, scale float64) float64 {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,13 +321,14 @@ func (u *UserInterface) setWindowMonitor(monitor *Monitor) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := dipToGLFWPixel(float64(ww), monitor)
|
s := monitor.DeviceScaleFactor()
|
||||||
h := dipToGLFWPixel(float64(wh), monitor)
|
w := dipToGLFWPixel(float64(ww), s)
|
||||||
|
h := dipToGLFWPixel(float64(wh), s)
|
||||||
mx := monitor.boundsInGLFWPixels.Min.X
|
mx := monitor.boundsInGLFWPixels.Min.X
|
||||||
my := monitor.boundsInGLFWPixels.Min.Y
|
my := monitor.boundsInGLFWPixels.Min.Y
|
||||||
mw, mh := monitor.sizeInDIP()
|
mw, mh := monitor.sizeInDIP()
|
||||||
mw = dipToGLFWPixel(mw, monitor)
|
mw = dipToGLFWPixel(mw, s)
|
||||||
mh = dipToGLFWPixel(mh, monitor)
|
mh = dipToGLFWPixel(mh, s)
|
||||||
px, py := InitialWindowPosition(int(mw), int(mh), int(w), int(h))
|
px, py := InitialWindowPosition(int(mw), int(mh), int(w), int(h))
|
||||||
if err := u.window.SetPos(mx+px, my+py); err != nil {
|
if err := u.window.SetPos(mx+px, my+py); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -843,8 +844,9 @@ func (u *UserInterface) createWindow() error {
|
|||||||
|
|
||||||
monitor := u.getInitMonitor()
|
monitor := u.getInitMonitor()
|
||||||
ww, wh := u.getInitWindowSizeInDIP()
|
ww, wh := u.getInitWindowSizeInDIP()
|
||||||
width := int(dipToGLFWPixel(float64(ww), monitor))
|
s := monitor.DeviceScaleFactor()
|
||||||
height := int(dipToGLFWPixel(float64(wh), monitor))
|
width := int(dipToGLFWPixel(float64(ww), s))
|
||||||
|
height := int(dipToGLFWPixel(float64(wh), s))
|
||||||
window, err := glfw.CreateWindow(width, height, "", nil, nil)
|
window, err := glfw.CreateWindow(width, height, "", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1240,8 +1242,9 @@ func (u *UserInterface) outsideSize() (float64, float64, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
w := dipFromGLFWPixel(float64(ww), m)
|
s := m.DeviceScaleFactor()
|
||||||
h := dipFromGLFWPixel(float64(wh), m)
|
w := dipFromGLFWPixel(float64(ww), s)
|
||||||
|
h := dipFromGLFWPixel(float64(wh), s)
|
||||||
return w, h, nil
|
return w, h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1318,6 +1321,9 @@ func (u *UserInterface) update() (float64, float64, error) {
|
|||||||
if err = u.window.Show(); err != nil {
|
if err = u.window.Show(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err = u.window.Focus(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
@ -1529,30 +1535,31 @@ func (u *UserInterface) updateWindowSizeLimits() error {
|
|||||||
}
|
}
|
||||||
minw, minh, maxw, maxh := u.getWindowSizeLimitsInDIP()
|
minw, minh, maxw, maxh := u.getWindowSizeLimitsInDIP()
|
||||||
|
|
||||||
|
s := m.DeviceScaleFactor()
|
||||||
if minw < 0 {
|
if minw < 0 {
|
||||||
// Always set the minimum window width.
|
// Always set the minimum window width.
|
||||||
mw, err := u.minimumWindowWidth()
|
mw, err := u.minimumWindowWidth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
minw = int(dipToGLFWPixel(float64(mw), m))
|
minw = int(dipToGLFWPixel(float64(mw), s))
|
||||||
} else {
|
} else {
|
||||||
minw = int(dipToGLFWPixel(float64(minw), m))
|
minw = int(dipToGLFWPixel(float64(minw), s))
|
||||||
}
|
}
|
||||||
if minh < 0 {
|
if minh < 0 {
|
||||||
minh = glfw.DontCare
|
minh = glfw.DontCare
|
||||||
} else {
|
} else {
|
||||||
minh = int(dipToGLFWPixel(float64(minh), m))
|
minh = int(dipToGLFWPixel(float64(minh), s))
|
||||||
}
|
}
|
||||||
if maxw < 0 {
|
if maxw < 0 {
|
||||||
maxw = glfw.DontCare
|
maxw = glfw.DontCare
|
||||||
} else {
|
} else {
|
||||||
maxw = int(dipToGLFWPixel(float64(maxw), m))
|
maxw = int(dipToGLFWPixel(float64(maxw), s))
|
||||||
}
|
}
|
||||||
if maxh < 0 {
|
if maxh < 0 {
|
||||||
maxh = glfw.DontCare
|
maxh = glfw.DontCare
|
||||||
} else {
|
} else {
|
||||||
maxh = int(dipToGLFWPixel(float64(maxh), m))
|
maxh = int(dipToGLFWPixel(float64(maxh), s))
|
||||||
}
|
}
|
||||||
if err := u.window.SetSizeLimits(minw, minh, maxw, maxh); err != nil {
|
if err := u.window.SetSizeLimits(minw, minh, maxw, maxh); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1640,8 +1647,9 @@ func (u *UserInterface) setWindowSizeInDIP(width, height int, callSetSize bool)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newW := int(dipToGLFWPixel(float64(width), m))
|
s := m.DeviceScaleFactor()
|
||||||
newH := int(dipToGLFWPixel(float64(height), m))
|
newW := int(dipToGLFWPixel(float64(width), s))
|
||||||
|
newH := int(dipToGLFWPixel(float64(height), s))
|
||||||
if oldW != newW || oldH != newH {
|
if oldW != newW || oldH != newH {
|
||||||
// Just after SetSize, GetSize is not reliable especially on Linux/UNIX.
|
// Just after SetSize, GetSize is not reliable especially on Linux/UNIX.
|
||||||
// Let's wait for FramebufferSize callback in any cases.
|
// Let's wait for FramebufferSize callback in any cases.
|
||||||
@ -1740,8 +1748,9 @@ func (u *UserInterface) setFullscreen(fullscreen bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ww := int(dipToGLFWPixel(float64(u.origWindowWidthInDIP), m))
|
s := m.DeviceScaleFactor()
|
||||||
wh := int(dipToGLFWPixel(float64(u.origWindowHeightInDIP), m))
|
ww := int(dipToGLFWPixel(float64(u.origWindowWidthInDIP), s))
|
||||||
|
wh := int(dipToGLFWPixel(float64(u.origWindowHeightInDIP), s))
|
||||||
if u.isNativeFullscreenAvailable() {
|
if u.isNativeFullscreenAvailable() {
|
||||||
if err := u.setNativeFullscreen(false); err != nil {
|
if err := u.setNativeFullscreen(false); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -2061,8 +2070,9 @@ func (u *UserInterface) setWindowPositionInDIP(x, y int, monitor *Monitor) error
|
|||||||
|
|
||||||
mx := monitor.boundsInGLFWPixels.Min.X
|
mx := monitor.boundsInGLFWPixels.Min.X
|
||||||
my := monitor.boundsInGLFWPixels.Min.Y
|
my := monitor.boundsInGLFWPixels.Min.Y
|
||||||
xf := dipToGLFWPixel(float64(x), monitor)
|
s := monitor.DeviceScaleFactor()
|
||||||
yf := dipToGLFWPixel(float64(y), monitor)
|
xf := dipToGLFWPixel(float64(x), s)
|
||||||
|
yf := dipToGLFWPixel(float64(y), s)
|
||||||
if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf), monitor); f {
|
if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf), monitor); f {
|
||||||
u.setOrigWindowPos(x, y)
|
u.setOrigWindowPos(x, y)
|
||||||
} else {
|
} else {
|
||||||
@ -2124,3 +2134,7 @@ func IsScreenTransparentAvailable() bool {
|
|||||||
func (u *UserInterface) RunOnMainThread(f func()) {
|
func (u *UserInterface) RunOnMainThread(f func()) {
|
||||||
u.mainThread.Call(f)
|
u.mainThread.Call(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
|
return dipToGLFWPixel(x, scale)
|
||||||
|
}
|
||||||
|
@ -92,3 +92,7 @@ func deviceScaleFactorImpl() float64 {
|
|||||||
// TODO: Can this be called from non-main threads?
|
// TODO: Can this be called from non-main threads?
|
||||||
return float64(C.devicePixelRatio())
|
return float64(C.devicePixelRatio())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
@ -811,3 +811,7 @@ func (u *UserInterface) updateIconIfNeeded() error {
|
|||||||
func IsScreenTransparentAvailable() bool {
|
func IsScreenTransparentAvailable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
@ -122,12 +122,12 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
|
|||||||
return physWidth, physHeight, nil
|
return physWidth, physHeight, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
func dipFromGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||||
return x / monitor.DeviceScaleFactor()
|
return x / deviceScaleFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
|
func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||||
return x * monitor.DeviceScaleFactor()
|
return x * deviceScaleFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||||
|
@ -181,3 +181,7 @@ func (u *UserInterface) Monitor() *Monitor {
|
|||||||
func IsScreenTransparentAvailable() bool {
|
func IsScreenTransparentAvailable() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
@ -174,3 +174,7 @@ func (u *UserInterface) Monitor() *Monitor {
|
|||||||
func IsScreenTransparentAvailable() bool {
|
func IsScreenTransparentAvailable() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dipToNativePixels(x float64, scale float64) float64 {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
@ -101,12 +101,12 @@ func glfwMonitorSizeInGLFWPixels(m *glfw.Monitor) (int, int, error) {
|
|||||||
return vm.Width, vm.Height, nil
|
return vm.Width, vm.Height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
func dipFromGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||||
return x / monitor.DeviceScaleFactor()
|
return x / deviceScaleFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
|
func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||||
return x * monitor.DeviceScaleFactor()
|
return x * deviceScaleFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||||
|
@ -322,8 +322,9 @@ func (w *glfwWindow) Position() (int, int) {
|
|||||||
}
|
}
|
||||||
wx -= m.boundsInGLFWPixels.Min.X
|
wx -= m.boundsInGLFWPixels.Min.X
|
||||||
wy -= m.boundsInGLFWPixels.Min.Y
|
wy -= m.boundsInGLFWPixels.Min.Y
|
||||||
xf := dipFromGLFWPixel(float64(wx), m)
|
s := m.DeviceScaleFactor()
|
||||||
yf := dipFromGLFWPixel(float64(wy), m)
|
xf := dipFromGLFWPixel(float64(wx), s)
|
||||||
|
yf := dipFromGLFWPixel(float64(wy), s)
|
||||||
x, y = int(xf), int(yf)
|
x, y = int(xf), int(yf)
|
||||||
})
|
})
|
||||||
return x, y
|
return x, y
|
||||||
|
7
keys.go
7
keys.go
@ -113,6 +113,7 @@ const (
|
|||||||
KeyF24 Key = Key(ui.KeyF24)
|
KeyF24 Key = Key(ui.KeyF24)
|
||||||
KeyHome Key = Key(ui.KeyHome)
|
KeyHome Key = Key(ui.KeyHome)
|
||||||
KeyInsert Key = Key(ui.KeyInsert)
|
KeyInsert Key = Key(ui.KeyInsert)
|
||||||
|
KeyIntlBackslash Key = Key(ui.KeyIntlBackslash)
|
||||||
KeyMetaLeft Key = Key(ui.KeyMetaLeft)
|
KeyMetaLeft Key = Key(ui.KeyMetaLeft)
|
||||||
KeyMetaRight Key = Key(ui.KeyMetaRight)
|
KeyMetaRight Key = Key(ui.KeyMetaRight)
|
||||||
KeyMinus Key = Key(ui.KeyMinus)
|
KeyMinus Key = Key(ui.KeyMinus)
|
||||||
@ -365,6 +366,8 @@ func (k Key) isValid() bool {
|
|||||||
return true
|
return true
|
||||||
case KeyInsert:
|
case KeyInsert:
|
||||||
return true
|
return true
|
||||||
|
case KeyIntlBackslash:
|
||||||
|
return true
|
||||||
case KeyMeta:
|
case KeyMeta:
|
||||||
return true
|
return true
|
||||||
case KeyMetaLeft:
|
case KeyMetaLeft:
|
||||||
@ -618,6 +621,8 @@ func (k Key) String() string {
|
|||||||
return "Home"
|
return "Home"
|
||||||
case KeyInsert:
|
case KeyInsert:
|
||||||
return "Insert"
|
return "Insert"
|
||||||
|
case KeyIntlBackslash:
|
||||||
|
return "IntlBackslash"
|
||||||
case KeyMeta:
|
case KeyMeta:
|
||||||
return "Meta"
|
return "Meta"
|
||||||
case KeyMetaLeft:
|
case KeyMetaLeft:
|
||||||
@ -892,6 +897,8 @@ func keyNameToKeyCode(name string) (Key, bool) {
|
|||||||
return KeyHome, true
|
return KeyHome, true
|
||||||
case "insert":
|
case "insert":
|
||||||
return KeyInsert, true
|
return KeyInsert, true
|
||||||
|
case "intlbackslash":
|
||||||
|
return KeyIntlBackslash, true
|
||||||
case "kp0":
|
case "kp0":
|
||||||
return KeyKP0, true
|
return KeyKP0, true
|
||||||
case "kp1":
|
case "kp1":
|
||||||
|
@ -117,7 +117,7 @@ var iosKeyToUIKey = map[int]ui.Key{
|
|||||||
97: ui.KeyNumpad9,
|
97: ui.KeyNumpad9,
|
||||||
98: ui.KeyNumpad0,
|
98: ui.KeyNumpad0,
|
||||||
99: ui.KeyNumpadDecimal,
|
99: ui.KeyNumpadDecimal,
|
||||||
100: ui.KeyBackslash,
|
100: ui.KeyIntlBackslash,
|
||||||
103: ui.KeyNumpadEqual,
|
103: ui.KeyNumpadEqual,
|
||||||
104: ui.KeyF13,
|
104: ui.KeyF13,
|
||||||
105: ui.KeyF14,
|
105: ui.KeyF14,
|
||||||
|
@ -2394,3 +2394,96 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue #2923
|
||||||
|
func TestShaderReturnArray(t *testing.T) {
|
||||||
|
const w, h = 16, 16
|
||||||
|
|
||||||
|
dst := ebiten.NewImage(w, h)
|
||||||
|
s, err := ebiten.NewShader([]byte(`//kage:unit pixels
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func foo() [4]float {
|
||||||
|
return [4]float{0.25, 0.5, 0.75, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||||
|
a := foo()
|
||||||
|
return vec4(a[0], a[1], a[2], a[3])
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.DrawRectShader(w, h, s, nil)
|
||||||
|
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
got := dst.At(i, j).(color.RGBA)
|
||||||
|
want := color.RGBA{R: 0x40, G: 0x80, B: 0xc0, A: 0xff}
|
||||||
|
if !sameColors(got, want, 2) {
|
||||||
|
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue #2798
|
||||||
|
func TestShaderInvalidPremultipliedAlphaColor(t *testing.T) {
|
||||||
|
// This test checks the rendering result when the shader returns an invalid premultiplied alpha color.
|
||||||
|
// The result values are kept and not clamped.
|
||||||
|
|
||||||
|
const w, h = 16, 16
|
||||||
|
|
||||||
|
dst := ebiten.NewImage(w, h)
|
||||||
|
s, err := ebiten.NewShader([]byte(`//kage:unit pixels
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||||
|
return vec4(1, 0.75, 0.5, 0.25)
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.DrawRectShader(w, h, s, nil)
|
||||||
|
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
got := dst.At(i, j).(color.RGBA)
|
||||||
|
want := color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0x40}
|
||||||
|
if !sameColors(got, want, 2) {
|
||||||
|
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.Clear()
|
||||||
|
s, err = ebiten.NewShader([]byte(`//kage:unit pixels
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
||||||
|
return vec4(1, 0.75, 0.5, 0)
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.DrawRectShader(w, h, s, nil)
|
||||||
|
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
got := dst.At(i, j).(color.RGBA)
|
||||||
|
want := color.RGBA{R: 0xff, G: 0xc0, B: 0x80, A: 0}
|
||||||
|
if !sameColors(got, want, 2) {
|
||||||
|
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,29 +25,29 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Face = (*StdFace)(nil)
|
var _ Face = (*GoXFace)(nil)
|
||||||
|
|
||||||
type stdFaceGlyphImageCacheKey struct {
|
type goXFaceGlyphImageCacheKey struct {
|
||||||
rune rune
|
rune rune
|
||||||
xoffset fixed.Int26_6
|
xoffset fixed.Int26_6
|
||||||
|
|
||||||
// yoffset is always the same if the rune is the same, so this doesn't have to be a key.
|
// yoffset is always the same if the rune is the same, so this doesn't have to be a key.
|
||||||
}
|
}
|
||||||
|
|
||||||
// StdFace is a Face implementation for a semi-standard font.Face (golang.org/x/image/font).
|
// GoXFace is a Face implementation for a semi-standard font.Face (golang.org/x/image/font).
|
||||||
// StdFace is useful to transit from existing codebase with text v1, or to use some bitmap fonts defined as font.Face.
|
// GoXFace is useful to transit from existing codebase with text v1, or to use some bitmap fonts defined as font.Face.
|
||||||
// StdFace must not be copied by value.
|
// GoXFace must not be copied by value.
|
||||||
type StdFace struct {
|
type GoXFace struct {
|
||||||
f *faceWithCache
|
f *faceWithCache
|
||||||
|
|
||||||
glyphImageCache glyphImageCache[stdFaceGlyphImageCacheKey]
|
glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]
|
||||||
|
|
||||||
addr *StdFace
|
addr *GoXFace
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStdFace creates a new StdFace from a semi-standard font.Face.
|
// NewGoXFace creates a new GoXFace from a semi-standard font.Face.
|
||||||
func NewStdFace(face font.Face) *StdFace {
|
func NewGoXFace(face font.Face) *GoXFace {
|
||||||
s := &StdFace{
|
s := &GoXFace{
|
||||||
f: &faceWithCache{
|
f: &faceWithCache{
|
||||||
f: face,
|
f: face,
|
||||||
},
|
},
|
||||||
@ -56,14 +56,14 @@ func NewStdFace(face font.Face) *StdFace {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StdFace) copyCheck() {
|
func (s *GoXFace) copyCheck() {
|
||||||
if s.addr != s {
|
if s.addr != s {
|
||||||
panic("text: illegal use of non-zero StdFace copied by value")
|
panic("text: illegal use of non-zero GoXFace copied by value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics implements Face.
|
// Metrics implements Face.
|
||||||
func (s *StdFace) Metrics() Metrics {
|
func (s *GoXFace) Metrics() Metrics {
|
||||||
s.copyCheck()
|
s.copyCheck()
|
||||||
|
|
||||||
m := s.f.Metrics()
|
m := s.f.Metrics()
|
||||||
@ -77,24 +77,24 @@ func (s *StdFace) Metrics() Metrics {
|
|||||||
// 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.
|
// This is unsafe since this might make internal cache states out of sync.
|
||||||
func (s *StdFace) UnsafeInternal() font.Face {
|
func (s *GoXFace) UnsafeInternal() font.Face {
|
||||||
s.copyCheck()
|
s.copyCheck()
|
||||||
return s.f.f
|
return s.f.f
|
||||||
}
|
}
|
||||||
|
|
||||||
// advance implements Face.
|
// advance implements Face.
|
||||||
func (s *StdFace) advance(text string) float64 {
|
func (s *GoXFace) advance(text string) float64 {
|
||||||
return fixed26_6ToFloat64(font.MeasureString(s.f, text))
|
return fixed26_6ToFloat64(font.MeasureString(s.f, text))
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasGlyph implements Face.
|
// hasGlyph implements Face.
|
||||||
func (s *StdFace) hasGlyph(r rune) bool {
|
func (s *GoXFace) hasGlyph(r rune) bool {
|
||||||
_, ok := s.f.GlyphAdvance(r)
|
_, ok := s.f.GlyphAdvance(r)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendGlyphsForLine implements Face.
|
// appendGlyphsForLine implements Face.
|
||||||
func (s *StdFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph {
|
func (s *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset int, originX, originY float64) []Glyph {
|
||||||
s.copyCheck()
|
s.copyCheck()
|
||||||
|
|
||||||
origin := fixed.Point26_6{
|
origin := fixed.Point26_6{
|
||||||
@ -129,8 +129,8 @@ func (s *StdFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
|
|||||||
return glyphs
|
return glyphs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
||||||
// Assume that StdFace's direction is always horizontal.
|
// Assume that GoXFace's direction is always horizontal.
|
||||||
origin.X = adjustGranularity(origin.X, s)
|
origin.X = adjustGranularity(origin.X, s)
|
||||||
origin.Y &^= ((1 << 6) - 1)
|
origin.Y &^= ((1 << 6) - 1)
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int
|
|||||||
X: (origin.X + b.Min.X) & ((1 << 6) - 1),
|
X: (origin.X + b.Min.X) & ((1 << 6) - 1),
|
||||||
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
||||||
}
|
}
|
||||||
key := stdFaceGlyphImageCacheKey{
|
key := goXFaceGlyphImageCacheKey{
|
||||||
rune: r,
|
rune: r,
|
||||||
xoffset: subpixelOffset.X,
|
xoffset: subpixelOffset.X,
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int
|
|||||||
return img, imgX, imgY, a
|
return img, imgX, imgY, a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StdFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
func (s *GoXFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBounds fixed.Rectangle26_6) *ebiten.Image {
|
||||||
w, h := (glyphBounds.Max.X - glyphBounds.Min.X).Ceil(), (glyphBounds.Max.Y - glyphBounds.Min.Y).Ceil()
|
w, h := (glyphBounds.Max.X - glyphBounds.Min.X).Ceil(), (glyphBounds.Max.Y - glyphBounds.Min.Y).Ceil()
|
||||||
if w == 0 || h == 0 {
|
if w == 0 || h == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -179,14 +179,14 @@ func (s *StdFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// direction implements Face.
|
// direction implements Face.
|
||||||
func (s *StdFace) direction() Direction {
|
func (s *GoXFace) direction() Direction {
|
||||||
return DirectionLeftToRight
|
return DirectionLeftToRight
|
||||||
}
|
}
|
||||||
|
|
||||||
// appendVectorPathForLine implements Face.
|
// appendVectorPathForLine implements Face.
|
||||||
func (s *StdFace) appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) {
|
func (s *GoXFace) appendVectorPathForLine(path *vector.Path, line string, originX, originY float64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics implements Face.
|
// Metrics implements Face.
|
||||||
func (s *StdFace) private() {
|
func (s *GoXFace) private() {
|
||||||
}
|
}
|
@ -89,7 +89,7 @@ type LayoutOptions struct {
|
|||||||
//
|
//
|
||||||
// For horizontal directions, the start and end depends on the face.
|
// For horizontal directions, the start and end depends on the face.
|
||||||
// If the face is GoTextFace, the start and the end depend on the Direction property.
|
// If the face is GoTextFace, the start and the end depend on the Direction property.
|
||||||
// If the face is StdFace, the start and the end are always left and right respectively.
|
// If the face is GoXFace, the start and the end are always left and right respectively.
|
||||||
//
|
//
|
||||||
// For vertical directions, the start and end are top and bottom respectively.
|
// For vertical directions, the start and end are top and bottom respectively.
|
||||||
//
|
//
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
|
|
||||||
var _ Face = (*LimitedFace)(nil)
|
var _ Face = (*LimitedFace)(nil)
|
||||||
|
|
||||||
|
// LimitedFace is a Face with glyph limitations.
|
||||||
type LimitedFace struct {
|
type LimitedFace struct {
|
||||||
face Face
|
face Face
|
||||||
unicodeRanges unicodeRanges
|
unicodeRanges unicodeRanges
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestMultiFace(t *testing.T) {
|
func TestMultiFace(t *testing.T) {
|
||||||
faces := []text.Face{text.NewStdFace(bitmapfont.Face)}
|
faces := []text.Face{text.NewGoXFace(bitmapfont.Face)}
|
||||||
f, err := text.NewMultiFace(faces...)
|
f, err := text.NewMultiFace(faces...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Face is an interface representing a font face.
|
// Face is an interface representing a font face.
|
||||||
// The implementations are only faces defined in this package, like GoTextFace and StdFace.
|
// The implementations are only faces defined in this package, like GoTextFace and GoXFace.
|
||||||
type Face interface {
|
type Face interface {
|
||||||
// Metrics returns the metrics for this Face.
|
// Metrics returns the metrics for this Face.
|
||||||
Metrics() Metrics
|
Metrics() Metrics
|
||||||
@ -59,15 +59,15 @@ type Metrics struct {
|
|||||||
HDescent float64
|
HDescent float64
|
||||||
|
|
||||||
// VLineGap is the recommended amount of horizontal space between two lines of text in pixels.
|
// VLineGap is the recommended amount of horizontal space between two lines of text in pixels.
|
||||||
// If the face is StdFace or the font doesn't support a vertical direction, VLineGap is 0.
|
// If the face is GoXFace or the font doesn't support a vertical direction, VLineGap is 0.
|
||||||
VLineGap float64
|
VLineGap float64
|
||||||
|
|
||||||
// VAscent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
// VAscent is the distance in pixels from the top of a line to its baseline for vertical lines.
|
||||||
// If the face is StdFace or the font doesn't support a vertical direction, VAscent is 0.
|
// If the face is GoXFace or the font doesn't support a vertical direction, VAscent is 0.
|
||||||
VAscent float64
|
VAscent float64
|
||||||
|
|
||||||
// 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 StdFace 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func TestGlyphIndex(t *testing.T) {
|
|||||||
const sampleText = `The quick brown fox jumps
|
const sampleText = `The quick brown fox jumps
|
||||||
over the lazy dog.`
|
over the lazy dog.`
|
||||||
|
|
||||||
f := text.NewStdFace(bitmapfont.Face)
|
f := text.NewGoXFace(bitmapfont.Face)
|
||||||
got := sampleText
|
got := sampleText
|
||||||
for _, g := range text.AppendGlyphs(nil, sampleText, f, nil) {
|
for _, g := range text.AppendGlyphs(nil, sampleText, f, nil) {
|
||||||
got = got[:g.StartIndexInBytes] + strings.Repeat(" ", g.EndIndexInBytes-g.StartIndexInBytes) + got[g.EndIndexInBytes:]
|
got = got[:g.StartIndexInBytes] + strings.Repeat(" ", g.EndIndexInBytes-g.StartIndexInBytes) + got[g.EndIndexInBytes:]
|
||||||
@ -55,7 +55,7 @@ func TestTextColor(t *testing.T) {
|
|||||||
op := &text.DrawOptions{}
|
op := &text.DrawOptions{}
|
||||||
op.GeoM.Translate(0, 0)
|
op.GeoM.Translate(0, 0)
|
||||||
op.ColorScale.ScaleWithColor(clr)
|
op.ColorScale.ScaleWithColor(clr)
|
||||||
text.Draw(img, "Hello", text.NewStdFace(bitmapfont.Face), op)
|
text.Draw(img, "Hello", text.NewGoXFace(bitmapfont.Face), op)
|
||||||
|
|
||||||
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
||||||
allTransparent := true
|
allTransparent := true
|
||||||
@ -77,12 +77,12 @@ func TestTextColor(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const testStdFaceSize = 6
|
const testGoXFaceSize = 6
|
||||||
|
|
||||||
type testStdFace struct{}
|
type testGoXFace struct{}
|
||||||
|
|
||||||
func (f *testStdFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
func (f *testGoXFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||||
dr = image.Rect(0, 0, testStdFaceSize, testStdFaceSize)
|
dr = image.Rect(0, 0, testGoXFaceSize, testGoXFaceSize)
|
||||||
a := image.NewAlpha(dr)
|
a := image.NewAlpha(dr)
|
||||||
switch r {
|
switch r {
|
||||||
case 'a':
|
case 'a':
|
||||||
@ -99,56 +99,56 @@ func (f *testStdFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mask = a
|
mask = a
|
||||||
advance = fixed.I(testStdFaceSize)
|
advance = fixed.I(testGoXFaceSize)
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testStdFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
func (f *testGoXFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||||
bounds = fixed.R(0, 0, testStdFaceSize, testStdFaceSize)
|
bounds = fixed.R(0, 0, testGoXFaceSize, testGoXFaceSize)
|
||||||
advance = fixed.I(testStdFaceSize)
|
advance = fixed.I(testGoXFaceSize)
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testStdFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
func (f *testGoXFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||||
return fixed.I(testStdFaceSize), true
|
return fixed.I(testGoXFaceSize), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testStdFace) Kern(r0, r1 rune) fixed.Int26_6 {
|
func (f *testGoXFace) Kern(r0, r1 rune) fixed.Int26_6 {
|
||||||
if r1 == 'b' {
|
if r1 == 'b' {
|
||||||
return fixed.I(-testStdFaceSize)
|
return fixed.I(-testGoXFaceSize)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testStdFace) Close() error {
|
func (f *testGoXFace) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testStdFace) Metrics() font.Metrics {
|
func (f *testGoXFace) Metrics() font.Metrics {
|
||||||
return font.Metrics{
|
return font.Metrics{
|
||||||
Height: fixed.I(testStdFaceSize),
|
Height: fixed.I(testGoXFaceSize),
|
||||||
Ascent: 0,
|
Ascent: 0,
|
||||||
Descent: fixed.I(testStdFaceSize),
|
Descent: fixed.I(testGoXFaceSize),
|
||||||
XHeight: 0,
|
XHeight: 0,
|
||||||
CapHeight: fixed.I(testStdFaceSize),
|
CapHeight: fixed.I(testGoXFaceSize),
|
||||||
CaretSlope: image.Pt(0, 1),
|
CaretSlope: image.Pt(0, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue #1378
|
// Issue #1378
|
||||||
func TestNegativeKern(t *testing.T) {
|
func TestNegativeKern(t *testing.T) {
|
||||||
f := text.NewStdFace(&testStdFace{})
|
f := text.NewGoXFace(&testGoXFace{})
|
||||||
dst := ebiten.NewImage(testStdFaceSize*2, testStdFaceSize)
|
dst := ebiten.NewImage(testGoXFaceSize*2, testGoXFaceSize)
|
||||||
|
|
||||||
// With testStdFace, 'b' is rendered at the previous position as 0xff.
|
// With testGoXFace, 'b' is rendered at the previous position as 0xff.
|
||||||
// 'a' is rendered at the current position as 0x80.
|
// 'a' is rendered at the current position as 0x80.
|
||||||
op := &text.DrawOptions{}
|
op := &text.DrawOptions{}
|
||||||
op.GeoM.Translate(0, 0)
|
op.GeoM.Translate(0, 0)
|
||||||
text.Draw(dst, "ab", f, op)
|
text.Draw(dst, "ab", f, op)
|
||||||
for j := 0; j < testStdFaceSize; j++ {
|
for j := 0; j < testGoXFaceSize; j++ {
|
||||||
for i := 0; i < testStdFaceSize; i++ {
|
for i := 0; i < testGoXFaceSize; i++ {
|
||||||
got := dst.At(i, j)
|
got := dst.At(i, j)
|
||||||
want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
||||||
if got != want {
|
if got != want {
|
||||||
@ -159,10 +159,10 @@ func TestNegativeKern(t *testing.T) {
|
|||||||
|
|
||||||
// The glyph 'a' should be treated correctly.
|
// The glyph 'a' should be treated correctly.
|
||||||
op = &text.DrawOptions{}
|
op = &text.DrawOptions{}
|
||||||
op.GeoM.Translate(testStdFaceSize, 0)
|
op.GeoM.Translate(testGoXFaceSize, 0)
|
||||||
text.Draw(dst, "a", f, op)
|
text.Draw(dst, "a", f, op)
|
||||||
for j := 0; j < testStdFaceSize; j++ {
|
for j := 0; j < testGoXFaceSize; j++ {
|
||||||
for i := testStdFaceSize; i < testStdFaceSize*2; i++ {
|
for i := testGoXFaceSize; i < testGoXFaceSize*2; i++ {
|
||||||
got := dst.At(i, j)
|
got := dst.At(i, j)
|
||||||
want := color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}
|
want := color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}
|
||||||
if got != want {
|
if got != want {
|
||||||
@ -172,12 +172,12 @@ func TestNegativeKern(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type unhashableStdFace func()
|
type unhashableGoXFace func()
|
||||||
|
|
||||||
const unhashableStdFaceSize = 10
|
const unhashableGoXFaceSize = 10
|
||||||
|
|
||||||
func (u *unhashableStdFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
func (u *unhashableGoXFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
||||||
dr = image.Rect(0, 0, unhashableStdFaceSize, unhashableStdFaceSize)
|
dr = image.Rect(0, 0, unhashableGoXFaceSize, unhashableGoXFaceSize)
|
||||||
a := image.NewAlpha(dr)
|
a := image.NewAlpha(dr)
|
||||||
for j := dr.Min.Y; j < dr.Max.Y; j++ {
|
for j := dr.Min.Y; j < dr.Max.Y; j++ {
|
||||||
for i := dr.Min.X; i < dr.Max.X; i++ {
|
for i := dr.Min.X; i < dr.Max.X; i++ {
|
||||||
@ -185,53 +185,53 @@ func (u *unhashableStdFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mask = a
|
mask = a
|
||||||
advance = fixed.I(unhashableStdFaceSize)
|
advance = fixed.I(unhashableGoXFaceSize)
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unhashableStdFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
func (u *unhashableGoXFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||||
bounds = fixed.R(0, 0, unhashableStdFaceSize, unhashableStdFaceSize)
|
bounds = fixed.R(0, 0, unhashableGoXFaceSize, unhashableGoXFaceSize)
|
||||||
advance = fixed.I(unhashableStdFaceSize)
|
advance = fixed.I(unhashableGoXFaceSize)
|
||||||
ok = true
|
ok = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unhashableStdFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
func (u *unhashableGoXFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||||
return fixed.I(unhashableStdFaceSize), true
|
return fixed.I(unhashableGoXFaceSize), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unhashableStdFace) Kern(r0, r1 rune) fixed.Int26_6 {
|
func (u *unhashableGoXFace) Kern(r0, r1 rune) fixed.Int26_6 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unhashableStdFace) Close() error {
|
func (u *unhashableGoXFace) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *unhashableStdFace) Metrics() font.Metrics {
|
func (u *unhashableGoXFace) Metrics() font.Metrics {
|
||||||
return font.Metrics{
|
return font.Metrics{
|
||||||
Height: fixed.I(unhashableStdFaceSize),
|
Height: fixed.I(unhashableGoXFaceSize),
|
||||||
Ascent: 0,
|
Ascent: 0,
|
||||||
Descent: fixed.I(unhashableStdFaceSize),
|
Descent: fixed.I(unhashableGoXFaceSize),
|
||||||
XHeight: 0,
|
XHeight: 0,
|
||||||
CapHeight: fixed.I(unhashableStdFaceSize),
|
CapHeight: fixed.I(unhashableGoXFaceSize),
|
||||||
CaretSlope: image.Pt(0, 1),
|
CaretSlope: image.Pt(0, 1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue #2669
|
// Issue #2669
|
||||||
func TestUnhashableFace(t *testing.T) {
|
func TestUnhashableFace(t *testing.T) {
|
||||||
var face unhashableStdFace
|
var face unhashableGoXFace
|
||||||
f := text.NewStdFace(&face)
|
f := text.NewGoXFace(&face)
|
||||||
dst := ebiten.NewImage(unhashableStdFaceSize*2, unhashableStdFaceSize*2)
|
dst := ebiten.NewImage(unhashableGoXFaceSize*2, unhashableGoXFaceSize*2)
|
||||||
text.Draw(dst, "a", f, nil)
|
text.Draw(dst, "a", f, nil)
|
||||||
|
|
||||||
for j := 0; j < unhashableStdFaceSize*2; j++ {
|
for j := 0; j < unhashableGoXFaceSize*2; j++ {
|
||||||
for i := 0; i < unhashableStdFaceSize*2; i++ {
|
for i := 0; i < unhashableGoXFaceSize*2; i++ {
|
||||||
got := dst.At(i, j)
|
got := dst.At(i, j)
|
||||||
var want color.RGBA
|
var want color.RGBA
|
||||||
if i < unhashableStdFaceSize && j < unhashableStdFaceSize {
|
if i < unhashableGoXFaceSize && j < unhashableGoXFaceSize {
|
||||||
want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
want = color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
||||||
}
|
}
|
||||||
if got != want {
|
if got != want {
|
||||||
|
Loading…
Reference in New Issue
Block a user