mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
31 Commits
c73ee2630a
...
0ddc018190
Author | SHA1 | Date | |
---|---|---|---|
|
0ddc018190 | ||
|
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 {
|
||||
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
|
||||
err error
|
||||
ready bool
|
||||
readyOnce sync.Once
|
||||
|
||||
playingPlayers map[*playerImpl]struct{}
|
||||
|
||||
@ -100,7 +94,6 @@ func NewContext(sampleRate int) *Context {
|
||||
sampleRate: sampleRate,
|
||||
playerFactory: newPlayerFactory(sampleRate),
|
||||
playingPlayers: map[*playerImpl]struct{}{},
|
||||
inited: make(chan struct{}),
|
||||
semaphore: make(chan struct{}, 1),
|
||||
}
|
||||
theContext = c
|
||||
@ -128,10 +121,6 @@ func NewContext(sampleRate int) *Context {
|
||||
})
|
||||
|
||||
h.AppendHookOnBeforeUpdate(func() error {
|
||||
c.initedOnce.Do(func() {
|
||||
close(c.inited)
|
||||
})
|
||||
|
||||
var err error
|
||||
theContextLock.Lock()
|
||||
if theContext != nil {
|
||||
@ -142,6 +131,19 @@ func NewContext(sampleRate int) *Context {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -287,28 +289,7 @@ func (c *Context) updatePlayers() error {
|
||||
func (c *Context) IsReady() bool {
|
||||
c.m.Lock()
|
||||
defer c.m.Unlock()
|
||||
|
||||
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
|
||||
return c.ready
|
||||
}
|
||||
|
||||
// SampleRate returns the sample rate.
|
||||
@ -316,18 +297,6 @@ func (c *Context) SampleRate() int {
|
||||
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.
|
||||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
type contextProxy struct {
|
||||
otoContext
|
||||
*oto.Context
|
||||
}
|
||||
|
||||
// NewPlayer implements context.
|
||||
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
|
||||
return
|
||||
}
|
||||
if !p.context.IsReady() {
|
||||
p.adjustedPosition = 0
|
||||
return
|
||||
}
|
||||
|
||||
samples := (p.stream.position() - int64(p.player.BufferedSize())) / bytesPerSampleInt16
|
||||
|
||||
|
@ -347,7 +347,8 @@ func (p *Player) draw(screen *ebiten.Image) {
|
||||
// Compose the current time text.
|
||||
m := (c / time.Minute) % 100
|
||||
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
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
|
@ -35,7 +35,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
inconsolataFace = text.NewStdFace(inconsolata.Bold8x16)
|
||||
inconsolataFace = text.NewGoXFace(inconsolata.Bold8x16)
|
||||
)
|
||||
|
||||
// mode is a blend mode with description.
|
||||
|
@ -35,7 +35,7 @@ const (
|
||||
screenHeight = 240
|
||||
)
|
||||
|
||||
var fontFace = text.NewStdFace(bitmapfont.Face)
|
||||
var fontFace = text.NewGoXFace(bitmapfont.Face)
|
||||
|
||||
var keyboardImage *ebiten.Image
|
||||
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/v2/vector"
|
||||
)
|
||||
|
||||
var fontFace = text.NewStdFace(bitmapfont.FaceEA)
|
||||
var fontFace = text.NewGoXFace(bitmapfont.FaceEA)
|
||||
|
||||
const (
|
||||
screenWidth = 640
|
||||
@ -40,16 +40,9 @@ const (
|
||||
)
|
||||
|
||||
type TextField struct {
|
||||
bounds image.Rectangle
|
||||
multilines bool
|
||||
text string
|
||||
selectionStart int
|
||||
selectionEnd int
|
||||
focused bool
|
||||
|
||||
ch chan textinput.State
|
||||
end func()
|
||||
state textinput.State
|
||||
bounds image.Rectangle
|
||||
multilines bool
|
||||
field textinput.Field
|
||||
}
|
||||
|
||||
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 {
|
||||
t.cleanUp()
|
||||
idx, ok := t.textIndexByCursorPosition(x, y)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
t.selectionStart = idx
|
||||
t.selectionEnd = idx
|
||||
t.field.SetSelection(idx, idx)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -95,20 +86,21 @@ func (t *TextField) textIndexByCursorPosition(x, y int) (int, bool) {
|
||||
var nlCount int
|
||||
var lineStart int
|
||||
var prevAdvance float64
|
||||
for i, r := range t.text {
|
||||
txt := t.field.Text()
|
||||
for i, r := range txt {
|
||||
var x0, x1 int
|
||||
currentAdvance := text.Advance(t.text[lineStart:i], fontFace)
|
||||
currentAdvance := text.Advance(txt[lineStart:i], fontFace)
|
||||
if lineStart < i {
|
||||
x0 = int((prevAdvance + currentAdvance) / 2)
|
||||
}
|
||||
if r == '\n' {
|
||||
x1 = int(math.MaxInt32)
|
||||
} else if i < len(t.text) {
|
||||
} else if i < len(txt) {
|
||||
nextI := i + 1
|
||||
for !utf8.ValidString(t.text[i:nextI]) {
|
||||
for !utf8.ValidString(txt[i:nextI]) {
|
||||
nextI++
|
||||
}
|
||||
nextAdvance := text.Advance(t.text[lineStart:nextI], fontFace)
|
||||
nextAdvance := text.Advance(txt[lineStart:nextI], fontFace)
|
||||
x1 = int((currentAdvance + nextAdvance) / 2)
|
||||
} else {
|
||||
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() {
|
||||
t.focused = true
|
||||
t.field.Focus()
|
||||
}
|
||||
|
||||
func (t *TextField) Blur() {
|
||||
t.focused = false
|
||||
t.field.Blur()
|
||||
}
|
||||
|
||||
func (t *TextField) cleanUp() {
|
||||
if t.ch != nil {
|
||||
select {
|
||||
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
|
||||
func (t *TextField) Update() error {
|
||||
if !t.field.IsFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var processed bool
|
||||
|
||||
// Text inputting can happen multiple times in one tick (1/60[s] by default).
|
||||
// Handle all of them.
|
||||
for {
|
||||
if t.ch == nil {
|
||||
x, y := t.bounds.Min.X, t.bounds.Min.Y
|
||||
cx, cy := t.cursorPos()
|
||||
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
|
||||
x, y := t.bounds.Min.X, t.bounds.Min.Y
|
||||
cx, cy := t.cursorPos()
|
||||
px, py := textFieldPadding()
|
||||
x += cx + px
|
||||
y += cy + py + int(fontFace.Metrics().HAscent)
|
||||
handled, err := t.field.HandleInput(x, y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if processed {
|
||||
return
|
||||
if handled {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case inpututil.IsKeyJustPressed(ebiten.KeyEnter):
|
||||
if t.multilines {
|
||||
t.text = t.text[:t.selectionStart] + "\n" + t.text[t.selectionEnd:]
|
||||
t.selectionStart += 1
|
||||
t.selectionEnd = t.selectionStart
|
||||
text := t.field.Text()
|
||||
selectionStart, selectionEnd := t.field.Selection()
|
||||
text = text[:selectionStart] + "\n" + text[selectionEnd:]
|
||||
selectionStart += len("\n")
|
||||
selectionEnd = selectionStart
|
||||
t.field.SetTextAndSelection(text, selectionStart, selectionEnd)
|
||||
}
|
||||
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.
|
||||
_, l := utf8.DecodeLastRuneInString(t.text[:t.selectionStart])
|
||||
t.text = t.text[:t.selectionStart-l] + t.text[t.selectionEnd:]
|
||||
t.selectionStart -= l
|
||||
_, l := utf8.DecodeLastRuneInString(text[:selectionStart])
|
||||
text = text[:selectionStart-l] + text[selectionEnd:]
|
||||
selectionStart -= l
|
||||
}
|
||||
t.selectionEnd = t.selectionStart
|
||||
selectionEnd = selectionStart
|
||||
t.field.SetTextAndSelection(text, selectionStart, selectionEnd)
|
||||
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.
|
||||
_, l := utf8.DecodeLastRuneInString(t.text[:t.selectionStart])
|
||||
t.selectionStart -= l
|
||||
_, l := utf8.DecodeLastRuneInString(text[:selectionStart])
|
||||
selectionStart -= l
|
||||
}
|
||||
t.selectionEnd = t.selectionStart
|
||||
t.field.SetTextAndSelection(text, selectionStart, selectionStart)
|
||||
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.
|
||||
_, l := utf8.DecodeRuneInString(t.text[t.selectionEnd:])
|
||||
t.selectionEnd += l
|
||||
_, l := utf8.DecodeRuneInString(text[selectionEnd:])
|
||||
selectionEnd += l
|
||||
}
|
||||
t.selectionStart = t.selectionEnd
|
||||
t.field.SetTextAndSelection(text, selectionEnd, selectionEnd)
|
||||
}
|
||||
|
||||
if !t.multilines {
|
||||
orig := t.text
|
||||
orig := t.field.Text()
|
||||
new := strings.ReplaceAll(orig, "\n", "")
|
||||
if new != orig {
|
||||
t.selectionStart -= strings.Count(orig[:t.selectionStart], "\n")
|
||||
t.selectionEnd -= strings.Count(orig[:t.selectionEnd], "\n")
|
||||
selectionStart, selectionEnd := t.field.Selection()
|
||||
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) {
|
||||
var nlCount int
|
||||
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' {
|
||||
nlCount++
|
||||
lastNLPos = i
|
||||
}
|
||||
}
|
||||
|
||||
txt := t.text[lastNLPos+1 : t.selectionStart]
|
||||
if t.state.Text != "" {
|
||||
txt += t.state.Text[:t.state.CompositionSelectionStartInBytes]
|
||||
}
|
||||
txt = txt[lastNLPos+1:]
|
||||
x := int(text.Advance(txt, fontFace))
|
||||
y := nlCount * int(fontFace.Metrics().HLineGap+fontFace.Metrics().HAscent+fontFace.Metrics().HDescent)
|
||||
return x, y
|
||||
@ -282,13 +228,14 @@ func (t *TextField) cursorPos() (int, int) {
|
||||
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)
|
||||
var clr color.Color = color.Black
|
||||
if t.focused {
|
||||
if t.field.IsFocused() {
|
||||
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)
|
||||
|
||||
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
|
||||
cx, cy := t.cursorPos()
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
ty := t.bounds.Min.Y + py
|
||||
op := &text.DrawOptions{}
|
||||
op.GeoM.Translate(float64(tx), float64(ty))
|
||||
op.ColorScale.ScaleWithColor(color.Black)
|
||||
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
|
||||
@ -348,7 +290,9 @@ func (g *Game) Update() error {
|
||||
}
|
||||
|
||||
for _, tf := range g.textFields {
|
||||
tf.Update()
|
||||
if err := tf.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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 is the low-level API. For most use cases, Field is easier to use.
|
||||
type State struct {
|
||||
// Text represents the current inputting text.
|
||||
Text string
|
||||
@ -37,14 +39,19 @@ type State struct {
|
||||
|
||||
// Committed reports whether the current Text is the settled text.
|
||||
Committed bool
|
||||
|
||||
// Error is an error that happens during text inputting.
|
||||
Error error
|
||||
}
|
||||
|
||||
// Start starts 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.
|
||||
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))
|
||||
}
|
||||
|
||||
|
@ -59,10 +59,7 @@ var theTextInput textInput
|
||||
func (t *textInput) Start(x, y int) (chan State, func()) {
|
||||
var session *session
|
||||
ui.Get().RunOnMainThread(func() {
|
||||
if t.session != nil {
|
||||
t.session.end()
|
||||
t.session = nil
|
||||
}
|
||||
t.end()
|
||||
C.start(C.int(x), C.int(y))
|
||||
session = newSession()
|
||||
t.session = session
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (!darwin && !js) || ios
|
||||
//go:build (!darwin && !js && !windows) || ios
|
||||
|
||||
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",
|
||||
"NumpadEnter": "KPEnter",
|
||||
"NumpadEqual": "KPEqual",
|
||||
"IntlBackslash": "World1",
|
||||
}
|
||||
|
||||
// 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{
|
||||
55: "Comma",
|
||||
56: "Period",
|
||||
@ -204,16 +212,16 @@ func init() {
|
||||
0x35: "Backquote",
|
||||
|
||||
// These three keys are:
|
||||
// - US backslash-pipe key (above return),
|
||||
// - non-US backslash key (next to left shift; on German layout this is the <>| key), and
|
||||
// - US backslash-pipe key, and
|
||||
// - 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.
|
||||
//
|
||||
// See also: https://www.w3.org/TR/uievents-code/#keyboard-102
|
||||
0x31: "Backslash", // UIKeyboardHIDUsageKeyboardBackslash
|
||||
0x64: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSBackslash
|
||||
0x32: "Backslash", // UIKeyboardHIDUsageKeyboardNonUSPound
|
||||
|
||||
0x64: "IntlBackslash", // UIKeyboardHIDUsageKeyboardNonUSBackslash
|
||||
|
||||
0x2A: "Backspace",
|
||||
0x2F: "BracketLeft",
|
||||
0x30: "BracketRight",
|
||||
@ -326,6 +334,7 @@ func init() {
|
||||
"NumpadEqual": "NumpadEqual",
|
||||
"MetaLeft": "MetaLeft",
|
||||
"MetaRight": "MetaRight",
|
||||
"IntlBackslash": "IntlBackslash",
|
||||
}
|
||||
|
||||
const (
|
||||
|
8
go.mod
8
go.mod
@ -3,11 +3,11 @@ module github.com/hajimehoshi/ebiten/v2
|
||||
go 1.18
|
||||
|
||||
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/oto/v3 v3.2.0-alpha.4
|
||||
github.com/ebitengine/purego v0.7.0-alpha.1
|
||||
github.com/go-text/typesetting v0.1.1-0.20231231232151-8d81c02dc157
|
||||
github.com/ebitengine/purego v0.7.0-alpha.3
|
||||
github.com/go-text/typesetting v0.1.1-0.20240317203452-bc341f663203
|
||||
github.com/hajimehoshi/bitmapfont/v3 v3.0.0
|
||||
github.com/hajimehoshi/go-mp3 v0.3.4
|
||||
github.com/jakecoffman/cp v1.2.1
|
||||
@ -16,7 +16,7 @@ require (
|
||||
github.com/kisielk/errcheck v1.6.3
|
||||
golang.org/x/image v0.15.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/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-20240223151600-9f1d75a9f41c/go.mod h1:8SdR2+sMMmYsei+c0ZW+AmJx2W6dPeSixWWSKRkJppA=
|
||||
github.com/ebitengine/gomobile v0.0.0-20240318151619-0eadfb33c201 h1:QlcxUcnQjv62kMxsh0NAQpwE0P454ec2i82DxmthMeI=
|
||||
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/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/go.mod h1:JtMbxJHZBDXfS8BmVYwzWk9Z6r7jsjwsHzOuZrEkfs4=
|
||||
github.com/ebitengine/purego v0.7.0-alpha.1 h1:Dlm9jM2kuzVQS89S1AALkDZQquow/Bbn3KVG68tFtWU=
|
||||
github.com/ebitengine/purego v0.7.0-alpha.1/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.20231231232151-8d81c02dc157/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
|
||||
github.com/ebitengine/purego v0.7.0-alpha.3 h1:9hH1aneqLaM3sM+PMUgRJVsMe2SqfVjZtV3DEzxBDJU=
|
||||
github.com/ebitengine/purego v0.7.0-alpha.3/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
|
||||
github.com/go-text/typesetting v0.1.1-0.20240317203452-bc341f663203 h1:dDnsjfTamLS74H+chc/GvLDFboY9BlK0Ztg5ibOzZ34=
|
||||
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-20240317173224-1986cbe96c66 h1:GUrm65PQPlhFSKjLPGOZNPNxLCybjzjYBzjfoBGaDUY=
|
||||
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/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-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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
|
||||
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,
|
||||
// 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.
|
||||
func (i *Image) DrawRectShader(width, height int, shader *Shader, options *DrawRectShaderOptions) {
|
||||
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.
|
||||
//
|
||||
// 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.
|
||||
func (i *Image) Set(x, y int, clr color.Color) {
|
||||
i.copyCheck()
|
||||
@ -1028,6 +1037,9 @@ func (i *Image) Deallocate() {
|
||||
//
|
||||
// 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.
|
||||
func (i *Image) WritePixels(pixels []byte) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -356,8 +356,13 @@ func (g *Gamepad) IsStandardButtonAvailable(button gamepaddb.StandardButton) boo
|
||||
// StandardAxisValue is concurrent-safe.
|
||||
func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
|
||||
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 {
|
||||
return m.Value()*2 - 1
|
||||
}
|
||||
@ -367,8 +372,13 @@ func (g *Gamepad) StandardAxisValue(axis gamepaddb.StandardAxis) float64 {
|
||||
// StandardButtonValue is concurrent-safe.
|
||||
func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
|
||||
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 {
|
||||
return m.Value()
|
||||
}
|
||||
@ -378,8 +388,13 @@ func (g *Gamepad) StandardButtonValue(button gamepaddb.StandardButton) float64 {
|
||||
// IsStandardButtonPressed is concurrent-safe.
|
||||
func (g *Gamepad) IsStandardButtonPressed(button gamepaddb.StandardButton) bool {
|
||||
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 {
|
||||
return m.Pressed()
|
||||
}
|
||||
|
@ -120,19 +120,19 @@ const (
|
||||
type mapping struct {
|
||||
Type mappingType
|
||||
Index int
|
||||
AxisScale int
|
||||
AxisOffset int
|
||||
AxisScale float64
|
||||
AxisOffset float64
|
||||
HatState int
|
||||
}
|
||||
|
||||
var (
|
||||
gamepadNames = map[string]string{}
|
||||
gamepadButtonMappings = map[string]map[StandardButton]*mapping{}
|
||||
gamepadAxisMappings = map[string]map[StandardAxis]*mapping{}
|
||||
gamepadButtonMappings = map[string]map[StandardButton]mapping{}
|
||||
gamepadAxisMappings = map[string]map[StandardAxis]mapping{}
|
||||
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)
|
||||
if len(line) == 0 {
|
||||
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 buttons == nil {
|
||||
buttons = map[StandardButton]*mapping{}
|
||||
buttons = map[StandardButton]mapping{}
|
||||
}
|
||||
buttons[b] = gb
|
||||
continue
|
||||
@ -200,7 +200,7 @@ func parseLine(line string, platform platform) (id string, name string, buttons
|
||||
|
||||
if a, ok := toStandardGamepadAxis(tks[0]); ok {
|
||||
if axes == nil {
|
||||
axes = map[StandardAxis]*mapping{}
|
||||
axes = map[StandardAxis]mapping{}
|
||||
}
|
||||
axes[a] = gb
|
||||
continue
|
||||
@ -213,7 +213,7 @@ func parseLine(line string, platform platform) (id string, name string, buttons
|
||||
return tokens[0], tokens[1], buttons, axes, nil
|
||||
}
|
||||
|
||||
func parseMappingElement(str string) (*mapping, error) {
|
||||
func parseMappingElement(str string) (mapping, error) {
|
||||
switch {
|
||||
case str[0] == 'a' || strings.HasPrefix(str, "+a") || strings.HasPrefix(str, "-a"):
|
||||
var tilda bool
|
||||
@ -222,8 +222,8 @@ func parseMappingElement(str string) (*mapping, error) {
|
||||
tilda = true
|
||||
}
|
||||
|
||||
min := -1
|
||||
max := 1
|
||||
min := -1.0
|
||||
max := 1.0
|
||||
numstr := str[1:]
|
||||
|
||||
if str[0] == '+' {
|
||||
@ -259,10 +259,10 @@ func parseMappingElement(str string) (*mapping, error) {
|
||||
|
||||
index, err := strconv.Atoi(numstr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return mapping{}, err
|
||||
}
|
||||
|
||||
return &mapping{
|
||||
return mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: index,
|
||||
AxisScale: scale,
|
||||
@ -272,9 +272,9 @@ func parseMappingElement(str string) (*mapping, error) {
|
||||
case str[0] == 'b':
|
||||
index, err := strconv.Atoi(str[1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return mapping{}, err
|
||||
}
|
||||
return &mapping{
|
||||
return mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: index,
|
||||
}, nil
|
||||
@ -282,24 +282,24 @@ func parseMappingElement(str string) (*mapping, error) {
|
||||
case str[0] == 'h':
|
||||
tokens := strings.Split(str[1:], ".")
|
||||
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])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return mapping{}, err
|
||||
}
|
||||
hat, err := strconv.Atoi(tokens[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return mapping{}, err
|
||||
}
|
||||
return &mapping{
|
||||
return mapping{
|
||||
Type: mappingTypeHat,
|
||||
Index: index,
|
||||
HatState: hat,
|
||||
}, 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) {
|
||||
@ -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 {
|
||||
return m
|
||||
}
|
||||
@ -370,7 +370,7 @@ func buttonMappings(id string) map[StandardButton]*mapping {
|
||||
return nil
|
||||
}
|
||||
|
||||
func axisMappings(id string) map[StandardAxis]*mapping {
|
||||
func axisMappings(id string) map[StandardAxis]mapping {
|
||||
if m, ok := gamepadAxisMappings[id]; ok {
|
||||
return m
|
||||
}
|
||||
@ -410,10 +410,11 @@ func HasStandardAxis(id string, axis StandardAxis) bool {
|
||||
if mappings == nil {
|
||||
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()
|
||||
defer mappingsM.RUnlock()
|
||||
|
||||
@ -422,14 +423,14 @@ func AxisValue(id string, axis StandardAxis, state GamepadState) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
mapping := mappings[axis]
|
||||
if mapping == nil {
|
||||
mapping, ok := mappings[axis]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch mapping.Type {
|
||||
case mappingTypeAxis:
|
||||
v := state.Axis(mapping.Index)*float64(mapping.AxisScale) + float64(mapping.AxisOffset)
|
||||
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
|
||||
if v > 1 {
|
||||
return 1
|
||||
} else if v < -1 {
|
||||
@ -461,30 +462,31 @@ func HasStandardButton(id string, button StandardButton) bool {
|
||||
if mappings == nil {
|
||||
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()
|
||||
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)
|
||||
if mappings == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
mapping := mappings[button]
|
||||
if mapping == nil {
|
||||
mapping, ok := mappings[button]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch mapping.Type {
|
||||
case mappingTypeAxis:
|
||||
v := state.Axis(mapping.Index)*float64(mapping.AxisScale) + float64(mapping.AxisOffset)
|
||||
v := state.Axis(mapping.Index)*mapping.AxisScale + mapping.AxisOffset
|
||||
if v > 1 {
|
||||
v = 1
|
||||
} else if v < -1 {
|
||||
@ -513,7 +515,7 @@ func buttonValue(id string, button StandardButton, state GamepadState) float64 {
|
||||
// Note: should be used with >, not >=, comparisons.
|
||||
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()
|
||||
defer mappingsM.RUnlock()
|
||||
|
||||
@ -522,14 +524,14 @@ func IsButtonPressed(id string, button StandardButton, state GamepadState) bool
|
||||
return false
|
||||
}
|
||||
|
||||
mapping := mappings[button]
|
||||
if mapping == nil {
|
||||
mapping, ok := mappings[button]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
switch mapping.Type {
|
||||
case mappingTypeAxis:
|
||||
v := buttonValue(id, button, state)
|
||||
v := standardButtonValue(id, button, state)
|
||||
return v > ButtonPressedThreshold
|
||||
case mappingTypeButton:
|
||||
return state.Button(mapping.Index)
|
||||
@ -554,8 +556,8 @@ func Update(mappingData []byte) error {
|
||||
type parsedLine struct {
|
||||
id string
|
||||
name string
|
||||
buttons map[StandardButton]*mapping
|
||||
axes map[StandardAxis]*mapping
|
||||
buttons map[StandardButton]mapping
|
||||
axes map[StandardAxis]mapping
|
||||
}
|
||||
var lines []parsedLine
|
||||
|
||||
@ -609,44 +611,44 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
gamepadButtonMappings[id] = map[StandardButton]*mapping{}
|
||||
gamepadAxisMappings[id] = map[StandardAxis]*mapping{}
|
||||
gamepadButtonMappings[id] = map[StandardButton]mapping{}
|
||||
gamepadAxisMappings[id] = map[StandardAxis]mapping{}
|
||||
|
||||
// For mappings, see mobile/ebitenmobileview/input_android.go.
|
||||
|
||||
if buttonMask&(1<<SDLControllerButtonA) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonRightBottom] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonRightBottom] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonA,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonB) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonRightRight] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonB,
|
||||
}
|
||||
} else {
|
||||
// Use the back button as "B" for easy UI navigation with TV remotes.
|
||||
gamepadButtonMappings[id][StandardButtonRightRight] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonRightRight] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonBack,
|
||||
}
|
||||
buttonMask &^= uint16(1) << SDLControllerButtonBack
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonX) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonRightLeft] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonRightLeft] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonX,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonY) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonRightTop] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonRightTop] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonY,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonBack) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonCenterLeft] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonCenterLeft] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonBack,
|
||||
}
|
||||
@ -654,69 +656,69 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
if buttonMask&(1<<SDLControllerButtonGuide) != 0 {
|
||||
// TODO: If SDKVersion >= 30, add this code:
|
||||
//
|
||||
// gamepadButtonMappings[id][StandardButtonCenterCenter] = &mapping{
|
||||
// gamepadButtonMappings[id][StandardButtonCenterCenter] = mapping{
|
||||
// Type: mappingTypeButton,
|
||||
// Index: SDLControllerButtonGuide,
|
||||
// }
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonStart) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonCenterRight] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonCenterRight] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonStart,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonLeftStick) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonLeftStick] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonLeftStick] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonLeftStick,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonRightStick) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonRightStick] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonRightStick] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonRightStick,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonLeftShoulder) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonFrontTopLeft] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonFrontTopLeft] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonLeftShoulder,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonRightShoulder) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonFrontTopRight] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonFrontTopRight] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonRightShoulder,
|
||||
}
|
||||
}
|
||||
|
||||
if buttonMask&(1<<SDLControllerButtonDpadUp) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonLeftTop] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonLeftTop] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonDpadUp,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonDpadDown) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonLeftBottom] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonLeftBottom] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonDpadDown,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonDpadLeft) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonLeftLeft] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonLeftLeft] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonDpadLeft,
|
||||
}
|
||||
}
|
||||
if buttonMask&(1<<SDLControllerButtonDpadRight) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonLeftRight] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonLeftRight] = mapping{
|
||||
Type: mappingTypeButton,
|
||||
Index: SDLControllerButtonDpadRight,
|
||||
}
|
||||
}
|
||||
|
||||
if axisMask&(1<<SDLControllerAxisLeftX) != 0 {
|
||||
gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = &mapping{
|
||||
gamepadAxisMappings[id][StandardAxisLeftStickHorizontal] = mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: SDLControllerAxisLeftX,
|
||||
AxisScale: 1,
|
||||
@ -724,7 +726,7 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
}
|
||||
}
|
||||
if axisMask&(1<<SDLControllerAxisLeftY) != 0 {
|
||||
gamepadAxisMappings[id][StandardAxisLeftStickVertical] = &mapping{
|
||||
gamepadAxisMappings[id][StandardAxisLeftStickVertical] = mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: SDLControllerAxisLeftY,
|
||||
AxisScale: 1,
|
||||
@ -732,7 +734,7 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
}
|
||||
}
|
||||
if axisMask&(1<<SDLControllerAxisRightX) != 0 {
|
||||
gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = &mapping{
|
||||
gamepadAxisMappings[id][StandardAxisRightStickHorizontal] = mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: SDLControllerAxisRightX,
|
||||
AxisScale: 1,
|
||||
@ -740,7 +742,7 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
}
|
||||
}
|
||||
if axisMask&(1<<SDLControllerAxisRightY) != 0 {
|
||||
gamepadAxisMappings[id][StandardAxisRightStickVertical] = &mapping{
|
||||
gamepadAxisMappings[id][StandardAxisRightStickVertical] = mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: SDLControllerAxisRightY,
|
||||
AxisScale: 1,
|
||||
@ -748,7 +750,7 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
}
|
||||
}
|
||||
if axisMask&(1<<SDLControllerAxisTriggerLeft) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonFrontBottomLeft] = mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: SDLControllerAxisTriggerLeft,
|
||||
AxisScale: 1,
|
||||
@ -756,7 +758,7 @@ func addAndroidDefaultMappings(id string) bool {
|
||||
}
|
||||
}
|
||||
if axisMask&(1<<SDLControllerAxisTriggerRight) != 0 {
|
||||
gamepadButtonMappings[id][StandardButtonFrontBottomRight] = &mapping{
|
||||
gamepadButtonMappings[id][StandardButtonFrontBottomRight] = mapping{
|
||||
Type: mappingTypeAxis,
|
||||
Index: SDLControllerAxisTriggerRight,
|
||||
AxisScale: 1,
|
||||
|
@ -73,6 +73,10 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
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]
|
||||
|
||||
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.
|
||||
var t shaderir.Type
|
||||
var finalType shaderir.Type
|
||||
switch callee.BuiltinFunc {
|
||||
case shaderir.BoolF:
|
||||
if err := checkArgsForBoolBuiltinFunc(args, argts); err != nil {
|
||||
cs.addError(e.Pos(), err.Error())
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Bool}
|
||||
finalType = shaderir.Type{Main: shaderir.Bool}
|
||||
case shaderir.IntF:
|
||||
if err := checkArgsForIntBuiltinFunc(args, argts); err != nil {
|
||||
cs.addError(e.Pos(), err.Error())
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Int}
|
||||
finalType = shaderir.Type{Main: shaderir.Int}
|
||||
case shaderir.FloatF:
|
||||
if err := checkArgsForFloatBuiltinFunc(args, argts); err != nil {
|
||||
cs.addError(e.Pos(), err.Error())
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Float}
|
||||
finalType = shaderir.Type{Main: shaderir.Float}
|
||||
case shaderir.Vec2F:
|
||||
if err := checkArgsForVec2BuiltinFunc(args, argts); err != nil {
|
||||
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)
|
||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Vec2}
|
||||
finalType = shaderir.Type{Main: shaderir.Vec2}
|
||||
case shaderir.Vec3F:
|
||||
if err := checkArgsForVec3BuiltinFunc(args, argts); err != nil {
|
||||
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)
|
||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Vec3}
|
||||
finalType = shaderir.Type{Main: shaderir.Vec3}
|
||||
case shaderir.Vec4F:
|
||||
if err := checkArgsForVec4BuiltinFunc(args, argts); err != nil {
|
||||
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)
|
||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Vec4}
|
||||
finalType = shaderir.Type{Main: shaderir.Vec4}
|
||||
case shaderir.IVec2F:
|
||||
if err := checkArgsForIVec2BuiltinFunc(args, argts); err != nil {
|
||||
cs.addError(e.Pos(), err.Error())
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.IVec2}
|
||||
finalType = shaderir.Type{Main: shaderir.IVec2}
|
||||
case shaderir.IVec3F:
|
||||
if err := checkArgsForIVec3BuiltinFunc(args, argts); err != nil {
|
||||
cs.addError(e.Pos(), err.Error())
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.IVec3}
|
||||
finalType = shaderir.Type{Main: shaderir.IVec3}
|
||||
case shaderir.IVec4F:
|
||||
if err := checkArgsForIVec4BuiltinFunc(args, argts); err != nil {
|
||||
cs.addError(e.Pos(), err.Error())
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.IVec4}
|
||||
finalType = shaderir.Type{Main: shaderir.IVec4}
|
||||
case shaderir.Mat2F:
|
||||
if err := checkArgsForMat2BuiltinFunc(args, argts); err != nil {
|
||||
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)
|
||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Mat2}
|
||||
finalType = shaderir.Type{Main: shaderir.Mat2}
|
||||
case shaderir.Mat3F:
|
||||
if err := checkArgsForMat3BuiltinFunc(args, argts); err != nil {
|
||||
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)
|
||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Mat3}
|
||||
finalType = shaderir.Type{Main: shaderir.Mat3}
|
||||
case shaderir.Mat4F:
|
||||
if err := checkArgsForMat4BuiltinFunc(args, argts); err != nil {
|
||||
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)
|
||||
argts[i] = shaderir.Type{Main: shaderir.Float}
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Mat4}
|
||||
finalType = shaderir.Type{Main: shaderir.Mat4}
|
||||
case shaderir.TexelAt:
|
||||
if len(args) != 2 {
|
||||
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))
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
t = shaderir.Type{Main: shaderir.Vec4}
|
||||
finalType = shaderir.Type{Main: shaderir.Vec4}
|
||||
case shaderir.DiscardF:
|
||||
if len(args) != 0 {
|
||||
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 {
|
||||
case shaderir.Clamp:
|
||||
if kind, allConsts := resolveConstKind(args, argts); allConsts {
|
||||
if kind, _ := resolveConstKind(args, argts); kind != gconstant.Unknown {
|
||||
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:
|
||||
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)
|
||||
if v.Kind() == gconstant.Unknown {
|
||||
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:
|
||||
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)
|
||||
if v.Kind() == gconstant.Unknown {
|
||||
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 {
|
||||
case shaderir.Smoothstep:
|
||||
t = argts[2]
|
||||
finalType = argts[2]
|
||||
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:
|
||||
@ -565,13 +580,17 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
||||
|
||||
switch callee.BuiltinFunc {
|
||||
case shaderir.Min, shaderir.Max:
|
||||
if kind, allConsts := resolveConstKind(args, argts); allConsts {
|
||||
if kind, _ := resolveConstKind(args, argts); kind != gconstant.Unknown {
|
||||
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:
|
||||
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)
|
||||
if v.Kind() == gconstant.Unknown {
|
||||
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:
|
||||
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)
|
||||
if v.Kind() == gconstant.Unknown {
|
||||
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 {
|
||||
case shaderir.Distance, shaderir.Dot:
|
||||
t = shaderir.Type{Main: shaderir.Float}
|
||||
finalType = shaderir.Type{Main: shaderir.Float}
|
||||
case shaderir.Step:
|
||||
t = argts[1]
|
||||
finalType = argts[1]
|
||||
default:
|
||||
t = argts[0]
|
||||
finalType = argts[0]
|
||||
}
|
||||
|
||||
default:
|
||||
@ -718,9 +744,9 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
||||
}
|
||||
}
|
||||
if callee.BuiltinFunc == shaderir.Length {
|
||||
t = shaderir.Type{Main: shaderir.Float}
|
||||
finalType = shaderir.Type{Main: shaderir.Float}
|
||||
} else {
|
||||
t = argts[0]
|
||||
finalType = argts[0]
|
||||
}
|
||||
}
|
||||
return []shaderir.Expr{
|
||||
@ -728,7 +754,7 @@ func (cs *compileState) parseExpr(block *block, fname string, expr ast.Expr, mar
|
||||
Type: shaderir.Call,
|
||||
Exprs: append([]shaderir.Expr{callee}, args...),
|
||||
},
|
||||
}, []shaderir.Type{t}, stmts, true
|
||||
}, []shaderir.Type{finalType}, stmts, true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
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 {
|
||||
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")
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
if len(ts) == 0 {
|
||||
cs.addError(e.Pos(), "unexpected index expression")
|
||||
return nil, nil, nil, false
|
||||
}
|
||||
x := exprs[0]
|
||||
t := ts[0]
|
||||
|
||||
@ -1169,8 +1203,24 @@ func resolveConstKind(exprs []shaderir.Expr, ts []shaderir.Type) (kind gconstant
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
allConsts = true
|
||||
for _, expr := range exprs {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
// For example, max(1.0, 1) should return a float value.
|
||||
if kind == gconstant.Unknown {
|
||||
for _, expr := range exprs {
|
||||
if expr.Const.Kind() == gconstant.Float {
|
||||
return gconstant.Float, true
|
||||
}
|
||||
for _, expr := range exprs {
|
||||
if expr.Const.Kind() == gconstant.Float {
|
||||
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
|
||||
out = nil
|
||||
}
|
||||
|
@ -2345,6 +2345,18 @@ func TestSyntaxBuiltinFuncDoubleArgsType(t *testing.T) {
|
||||
{stmt: "a := {{.Func}}(1, 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}}(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}}(1, vec2(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.0, 1); _ = 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}}(1, vec2(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, 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.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.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}}(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, vec3(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, 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.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), vec3(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.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), vec3(1)); _ = a", err: false},
|
||||
{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.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), vec3(1)); _ = a", err: true},
|
||||
{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, 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, 1.0, 1); 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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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];
|
||||
|
||||
vec2[2] F0(void);
|
||||
vec2[2] F1(void);
|
||||
void F0(out vec2 l0[2]);
|
||||
void F1(out vec2 l0[2]);
|
||||
|
||||
vec2[2] F0(void) {
|
||||
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);
|
||||
void F0(out vec2 l0[2]) {
|
||||
vec2 l1[2];
|
||||
l1[0] = vec2(0);
|
||||
l1[1] = vec2(0);
|
||||
(l0)[0] = vec2(1.0);
|
||||
l1[0] = l0[0];
|
||||
l1[1] = l0[1];
|
||||
(l1)[1] = vec2(2.0);
|
||||
return l1;
|
||||
l0[0] = l1[0];
|
||||
l0[1] = l1[1];
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
array<float2, 2> l0 = {};
|
||||
array<float2, 3> l1 = {};
|
||||
void F0(thread array<float2, 3>& l0) {
|
||||
array<float2, 2> l1 = {};
|
||||
array<float2, 3> l2 = {};
|
||||
{
|
||||
array<float2, 2> l1 = {};
|
||||
l1 = l0;
|
||||
array<float2, 2> l2 = {};
|
||||
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) {
|
||||
vec2 l0[2];
|
||||
l0[0] = vec2(0);
|
||||
l0[1] = vec2(0);
|
||||
vec2 l1[3];
|
||||
void F0(out vec2 l0[3]) {
|
||||
vec2 l1[2];
|
||||
l1[0] = 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];
|
||||
l1[0] = vec2(0);
|
||||
l1[1] = vec2(0);
|
||||
l1[0] = l0[0];
|
||||
l1[1] = l0[1];
|
||||
vec2 l2[2];
|
||||
l2[0] = vec2(0);
|
||||
l2[1] = vec2(0);
|
||||
l2[0] = l1[0];
|
||||
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);
|
||||
int[1] F1(void);
|
||||
void F0(out float l0[1]);
|
||||
void F1(out int l0[1]);
|
||||
|
||||
float[1] F0(void) {
|
||||
float l0[1];
|
||||
l0[0] = float(0);
|
||||
(l0)[0] = 1.0;
|
||||
return l0;
|
||||
void F0(out float l0[1]) {
|
||||
float l1[1];
|
||||
l1[0] = float(0);
|
||||
(l1)[0] = 1.0;
|
||||
l0[0] = l1[0];
|
||||
return;
|
||||
}
|
||||
|
||||
int[1] F1(void) {
|
||||
int l0[1];
|
||||
l0[0] = 0;
|
||||
(l0)[0] = 1;
|
||||
return l0;
|
||||
void F1(out int l0[1]) {
|
||||
int l1[1];
|
||||
l1[0] = 0;
|
||||
(l1)[0] = 1;
|
||||
l0[0] = l1[0];
|
||||
return;
|
||||
}
|
||||
|
@ -285,6 +285,10 @@ func (c *context) screenScaleAndOffsets() (scale, offsetX, offsetY float64) {
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UserInterface) LogicalPositionToClientPosition(x, y float64) (float64, float64) {
|
||||
return u.context.logicalPositionToClientPosition(x, y, u.Monitor().DeviceScaleFactor())
|
||||
func (u *UserInterface) LogicalPositionToClientPositionInNativePixels(x, y float64) (float64, float64) {
|
||||
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) {
|
||||
cx2, cy2 := u.context.logicalPositionToClientPosition(cx, cy, s)
|
||||
cx2 = dipToGLFWPixel(cx2, m)
|
||||
cy2 = dipToGLFWPixel(cy2, m)
|
||||
cx2 = dipToGLFWPixel(cx2, s)
|
||||
cy2 = dipToGLFWPixel(cy2, s)
|
||||
if err := u.window.SetCursorPos(cx2, cy2); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -106,8 +106,8 @@ func (u *UserInterface) updateInputStateImpl() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cx2 = dipFromGLFWPixel(cx2, m)
|
||||
cy2 = dipFromGLFWPixel(cy2, m)
|
||||
cx2 = dipFromGLFWPixel(cx2, s)
|
||||
cy2 = dipFromGLFWPixel(cy2, s)
|
||||
cx, cy = u.context.clientPositionToLogicalPosition(cx2, cy2, s)
|
||||
}
|
||||
|
||||
|
@ -106,6 +106,7 @@ const (
|
||||
KeyF24
|
||||
KeyHome
|
||||
KeyInsert
|
||||
KeyIntlBackslash
|
||||
KeyMetaLeft
|
||||
KeyMetaRight
|
||||
KeyMinus
|
||||
@ -315,6 +316,8 @@ func (k Key) String() string {
|
||||
return "KeyHome"
|
||||
case KeyInsert:
|
||||
return "KeyInsert"
|
||||
case KeyIntlBackslash:
|
||||
return "KeyIntlBackslash"
|
||||
case KeyMetaLeft:
|
||||
return "KeyMetaLeft"
|
||||
case KeyMetaRight:
|
||||
|
@ -89,6 +89,7 @@ var uiKeyToGLFWKey = map[Key]glfw.Key{
|
||||
KeyHome: glfw.KeyHome,
|
||||
KeyI: glfw.KeyI,
|
||||
KeyInsert: glfw.KeyInsert,
|
||||
KeyIntlBackslash: glfw.KeyWorld1,
|
||||
KeyJ: glfw.KeyJ,
|
||||
KeyK: glfw.KeyK,
|
||||
KeyL: glfw.KeyL,
|
||||
|
@ -87,6 +87,7 @@ var uiKeyToJSCode = map[Key]js.Value{
|
||||
KeyHome: js.ValueOf("Home"),
|
||||
KeyI: js.ValueOf("KeyI"),
|
||||
KeyInsert: js.ValueOf("Insert"),
|
||||
KeyIntlBackslash: js.ValueOf("IntlBackslash"),
|
||||
KeyJ: js.ValueOf("KeyJ"),
|
||||
KeyK: js.ValueOf("KeyK"),
|
||||
KeyL: js.ValueOf("KeyL"),
|
||||
|
@ -52,7 +52,8 @@ func (m *Monitor) DeviceScaleFactor() float64 {
|
||||
|
||||
func (m *Monitor) sizeInDIP() (float64, float64) {
|
||||
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 {
|
||||
|
@ -129,3 +129,7 @@ func deviceScaleFactorImpl() float64 {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
func dipFromGLFWPixel(x float64, scale float64) float64 {
|
||||
// NOTE: On macOS, GLFW exposes the device independent coordinate system.
|
||||
// Thus, the conversion functions are unnecessary,
|
||||
// however we still need the deviceScaleFactor internally
|
||||
@ -214,7 +214,7 @@ func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
func dipToGLFWPixel(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
||||
|
@ -321,13 +321,14 @@ func (u *UserInterface) setWindowMonitor(monitor *Monitor) error {
|
||||
}
|
||||
}
|
||||
|
||||
w := dipToGLFWPixel(float64(ww), monitor)
|
||||
h := dipToGLFWPixel(float64(wh), monitor)
|
||||
s := monitor.DeviceScaleFactor()
|
||||
w := dipToGLFWPixel(float64(ww), s)
|
||||
h := dipToGLFWPixel(float64(wh), s)
|
||||
mx := monitor.boundsInGLFWPixels.Min.X
|
||||
my := monitor.boundsInGLFWPixels.Min.Y
|
||||
mw, mh := monitor.sizeInDIP()
|
||||
mw = dipToGLFWPixel(mw, monitor)
|
||||
mh = dipToGLFWPixel(mh, monitor)
|
||||
mw = dipToGLFWPixel(mw, s)
|
||||
mh = dipToGLFWPixel(mh, s)
|
||||
px, py := InitialWindowPosition(int(mw), int(mh), int(w), int(h))
|
||||
if err := u.window.SetPos(mx+px, my+py); err != nil {
|
||||
return err
|
||||
@ -843,8 +844,9 @@ func (u *UserInterface) createWindow() error {
|
||||
|
||||
monitor := u.getInitMonitor()
|
||||
ww, wh := u.getInitWindowSizeInDIP()
|
||||
width := int(dipToGLFWPixel(float64(ww), monitor))
|
||||
height := int(dipToGLFWPixel(float64(wh), monitor))
|
||||
s := monitor.DeviceScaleFactor()
|
||||
width := int(dipToGLFWPixel(float64(ww), s))
|
||||
height := int(dipToGLFWPixel(float64(wh), s))
|
||||
window, err := glfw.CreateWindow(width, height, "", nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -1240,8 +1242,9 @@ func (u *UserInterface) outsideSize() (float64, float64, error) {
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
w := dipFromGLFWPixel(float64(ww), m)
|
||||
h := dipFromGLFWPixel(float64(wh), m)
|
||||
s := m.DeviceScaleFactor()
|
||||
w := dipFromGLFWPixel(float64(ww), s)
|
||||
h := dipFromGLFWPixel(float64(wh), s)
|
||||
return w, h, nil
|
||||
}
|
||||
|
||||
@ -1318,6 +1321,9 @@ func (u *UserInterface) update() (float64, float64, error) {
|
||||
if err = u.window.Show(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = u.window.Focus(); err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
@ -1529,30 +1535,31 @@ func (u *UserInterface) updateWindowSizeLimits() error {
|
||||
}
|
||||
minw, minh, maxw, maxh := u.getWindowSizeLimitsInDIP()
|
||||
|
||||
s := m.DeviceScaleFactor()
|
||||
if minw < 0 {
|
||||
// Always set the minimum window width.
|
||||
mw, err := u.minimumWindowWidth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
minw = int(dipToGLFWPixel(float64(mw), m))
|
||||
minw = int(dipToGLFWPixel(float64(mw), s))
|
||||
} else {
|
||||
minw = int(dipToGLFWPixel(float64(minw), m))
|
||||
minw = int(dipToGLFWPixel(float64(minw), s))
|
||||
}
|
||||
if minh < 0 {
|
||||
minh = glfw.DontCare
|
||||
} else {
|
||||
minh = int(dipToGLFWPixel(float64(minh), m))
|
||||
minh = int(dipToGLFWPixel(float64(minh), s))
|
||||
}
|
||||
if maxw < 0 {
|
||||
maxw = glfw.DontCare
|
||||
} else {
|
||||
maxw = int(dipToGLFWPixel(float64(maxw), m))
|
||||
maxw = int(dipToGLFWPixel(float64(maxw), s))
|
||||
}
|
||||
if maxh < 0 {
|
||||
maxh = glfw.DontCare
|
||||
} else {
|
||||
maxh = int(dipToGLFWPixel(float64(maxh), m))
|
||||
maxh = int(dipToGLFWPixel(float64(maxh), s))
|
||||
}
|
||||
if err := u.window.SetSizeLimits(minw, minh, maxw, maxh); err != nil {
|
||||
return err
|
||||
@ -1640,8 +1647,9 @@ func (u *UserInterface) setWindowSizeInDIP(width, height int, callSetSize bool)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newW := int(dipToGLFWPixel(float64(width), m))
|
||||
newH := int(dipToGLFWPixel(float64(height), m))
|
||||
s := m.DeviceScaleFactor()
|
||||
newW := int(dipToGLFWPixel(float64(width), s))
|
||||
newH := int(dipToGLFWPixel(float64(height), s))
|
||||
if oldW != newW || oldH != newH {
|
||||
// Just after SetSize, GetSize is not reliable especially on Linux/UNIX.
|
||||
// Let's wait for FramebufferSize callback in any cases.
|
||||
@ -1740,8 +1748,9 @@ func (u *UserInterface) setFullscreen(fullscreen bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ww := int(dipToGLFWPixel(float64(u.origWindowWidthInDIP), m))
|
||||
wh := int(dipToGLFWPixel(float64(u.origWindowHeightInDIP), m))
|
||||
s := m.DeviceScaleFactor()
|
||||
ww := int(dipToGLFWPixel(float64(u.origWindowWidthInDIP), s))
|
||||
wh := int(dipToGLFWPixel(float64(u.origWindowHeightInDIP), s))
|
||||
if u.isNativeFullscreenAvailable() {
|
||||
if err := u.setNativeFullscreen(false); err != nil {
|
||||
return err
|
||||
@ -2061,8 +2070,9 @@ func (u *UserInterface) setWindowPositionInDIP(x, y int, monitor *Monitor) error
|
||||
|
||||
mx := monitor.boundsInGLFWPixels.Min.X
|
||||
my := monitor.boundsInGLFWPixels.Min.Y
|
||||
xf := dipToGLFWPixel(float64(x), monitor)
|
||||
yf := dipToGLFWPixel(float64(y), monitor)
|
||||
s := monitor.DeviceScaleFactor()
|
||||
xf := dipToGLFWPixel(float64(x), s)
|
||||
yf := dipToGLFWPixel(float64(y), s)
|
||||
if x, y := u.adjustWindowPosition(mx+int(xf), my+int(yf), monitor); f {
|
||||
u.setOrigWindowPos(x, y)
|
||||
} else {
|
||||
@ -2124,3 +2134,7 @@ func IsScreenTransparentAvailable() bool {
|
||||
func (u *UserInterface) RunOnMainThread(f func()) {
|
||||
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?
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
return x / monitor.DeviceScaleFactor()
|
||||
func dipFromGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x / deviceScaleFactor
|
||||
}
|
||||
|
||||
func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
return x * monitor.DeviceScaleFactor()
|
||||
func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x * deviceScaleFactor
|
||||
}
|
||||
|
||||
func (u *UserInterface) adjustWindowPosition(x, y int, monitor *Monitor) (int, int) {
|
||||
|
@ -181,3 +181,7 @@ func (u *UserInterface) Monitor() *Monitor {
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func dipToNativePixels(x float64, scale float64) float64 {
|
||||
return x
|
||||
}
|
||||
|
@ -174,3 +174,7 @@ func (u *UserInterface) Monitor() *Monitor {
|
||||
func IsScreenTransparentAvailable() bool {
|
||||
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
|
||||
}
|
||||
|
||||
func dipFromGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
return x / monitor.DeviceScaleFactor()
|
||||
func dipFromGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x / deviceScaleFactor
|
||||
}
|
||||
|
||||
func dipToGLFWPixel(x float64, monitor *Monitor) float64 {
|
||||
return x * monitor.DeviceScaleFactor()
|
||||
func dipToGLFWPixel(x float64, deviceScaleFactor float64) float64 {
|
||||
return x * deviceScaleFactor
|
||||
}
|
||||
|
||||
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
|
||||
wy -= m.boundsInGLFWPixels.Min.Y
|
||||
xf := dipFromGLFWPixel(float64(wx), m)
|
||||
yf := dipFromGLFWPixel(float64(wy), m)
|
||||
s := m.DeviceScaleFactor()
|
||||
xf := dipFromGLFWPixel(float64(wx), s)
|
||||
yf := dipFromGLFWPixel(float64(wy), s)
|
||||
x, y = int(xf), int(yf)
|
||||
})
|
||||
return x, y
|
||||
|
7
keys.go
7
keys.go
@ -113,6 +113,7 @@ const (
|
||||
KeyF24 Key = Key(ui.KeyF24)
|
||||
KeyHome Key = Key(ui.KeyHome)
|
||||
KeyInsert Key = Key(ui.KeyInsert)
|
||||
KeyIntlBackslash Key = Key(ui.KeyIntlBackslash)
|
||||
KeyMetaLeft Key = Key(ui.KeyMetaLeft)
|
||||
KeyMetaRight Key = Key(ui.KeyMetaRight)
|
||||
KeyMinus Key = Key(ui.KeyMinus)
|
||||
@ -365,6 +366,8 @@ func (k Key) isValid() bool {
|
||||
return true
|
||||
case KeyInsert:
|
||||
return true
|
||||
case KeyIntlBackslash:
|
||||
return true
|
||||
case KeyMeta:
|
||||
return true
|
||||
case KeyMetaLeft:
|
||||
@ -618,6 +621,8 @@ func (k Key) String() string {
|
||||
return "Home"
|
||||
case KeyInsert:
|
||||
return "Insert"
|
||||
case KeyIntlBackslash:
|
||||
return "IntlBackslash"
|
||||
case KeyMeta:
|
||||
return "Meta"
|
||||
case KeyMetaLeft:
|
||||
@ -892,6 +897,8 @@ func keyNameToKeyCode(name string) (Key, bool) {
|
||||
return KeyHome, true
|
||||
case "insert":
|
||||
return KeyInsert, true
|
||||
case "intlbackslash":
|
||||
return KeyIntlBackslash, true
|
||||
case "kp0":
|
||||
return KeyKP0, true
|
||||
case "kp1":
|
||||
|
@ -117,7 +117,7 @@ var iosKeyToUIKey = map[int]ui.Key{
|
||||
97: ui.KeyNumpad9,
|
||||
98: ui.KeyNumpad0,
|
||||
99: ui.KeyNumpadDecimal,
|
||||
100: ui.KeyBackslash,
|
||||
100: ui.KeyIntlBackslash,
|
||||
103: ui.KeyNumpadEqual,
|
||||
104: ui.KeyF13,
|
||||
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"
|
||||
)
|
||||
|
||||
var _ Face = (*StdFace)(nil)
|
||||
var _ Face = (*GoXFace)(nil)
|
||||
|
||||
type stdFaceGlyphImageCacheKey struct {
|
||||
type goXFaceGlyphImageCacheKey struct {
|
||||
rune rune
|
||||
xoffset fixed.Int26_6
|
||||
|
||||
// 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).
|
||||
// StdFace 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.
|
||||
type StdFace struct {
|
||||
// GoXFace is a Face implementation for a semi-standard font.Face (golang.org/x/image/font).
|
||||
// GoXFace is useful to transit from existing codebase with text v1, or to use some bitmap fonts defined as font.Face.
|
||||
// GoXFace must not be copied by value.
|
||||
type GoXFace struct {
|
||||
f *faceWithCache
|
||||
|
||||
glyphImageCache glyphImageCache[stdFaceGlyphImageCacheKey]
|
||||
glyphImageCache glyphImageCache[goXFaceGlyphImageCacheKey]
|
||||
|
||||
addr *StdFace
|
||||
addr *GoXFace
|
||||
}
|
||||
|
||||
// NewStdFace creates a new StdFace from a semi-standard font.Face.
|
||||
func NewStdFace(face font.Face) *StdFace {
|
||||
s := &StdFace{
|
||||
// NewGoXFace creates a new GoXFace from a semi-standard font.Face.
|
||||
func NewGoXFace(face font.Face) *GoXFace {
|
||||
s := &GoXFace{
|
||||
f: &faceWithCache{
|
||||
f: face,
|
||||
},
|
||||
@ -56,14 +56,14 @@ func NewStdFace(face font.Face) *StdFace {
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *StdFace) copyCheck() {
|
||||
func (s *GoXFace) copyCheck() {
|
||||
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.
|
||||
func (s *StdFace) Metrics() Metrics {
|
||||
func (s *GoXFace) Metrics() Metrics {
|
||||
s.copyCheck()
|
||||
|
||||
m := s.f.Metrics()
|
||||
@ -77,24 +77,24 @@ func (s *StdFace) Metrics() Metrics {
|
||||
// UnsafeInternal returns its internal font.Face.
|
||||
//
|
||||
// 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()
|
||||
return s.f.f
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// hasGlyph implements Face.
|
||||
func (s *StdFace) hasGlyph(r rune) bool {
|
||||
func (s *GoXFace) hasGlyph(r rune) bool {
|
||||
_, ok := s.f.GlyphAdvance(r)
|
||||
return ok
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
origin := fixed.Point26_6{
|
||||
@ -129,8 +129,8 @@ func (s *StdFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i
|
||||
return glyphs
|
||||
}
|
||||
|
||||
func (s *StdFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
||||
// Assume that StdFace's direction is always horizontal.
|
||||
func (s *GoXFace) glyphImage(r rune, origin fixed.Point26_6) (*ebiten.Image, int, int, fixed.Int26_6) {
|
||||
// Assume that GoXFace's direction is always horizontal.
|
||||
origin.X = adjustGranularity(origin.X, s)
|
||||
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),
|
||||
Y: (origin.Y + b.Min.Y) & ((1 << 6) - 1),
|
||||
}
|
||||
key := stdFaceGlyphImageCacheKey{
|
||||
key := goXFaceGlyphImageCacheKey{
|
||||
rune: r,
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
if w == 0 || h == 0 {
|
||||
return nil
|
||||
@ -179,14 +179,14 @@ func (s *StdFace) glyphImageImpl(r rune, subpixelOffset fixed.Point26_6, glyphBo
|
||||
}
|
||||
|
||||
// direction implements Face.
|
||||
func (s *StdFace) direction() Direction {
|
||||
func (s *GoXFace) direction() Direction {
|
||||
return DirectionLeftToRight
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
// 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.
|
||||
//
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
|
||||
var _ Face = (*LimitedFace)(nil)
|
||||
|
||||
// LimitedFace is a Face with glyph limitations.
|
||||
type LimitedFace struct {
|
||||
face Face
|
||||
unicodeRanges unicodeRanges
|
||||
|
@ -26,7 +26,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMultiFace(t *testing.T) {
|
||||
faces := []text.Face{text.NewStdFace(bitmapfont.Face)}
|
||||
faces := []text.Face{text.NewGoXFace(bitmapfont.Face)}
|
||||
f, err := text.NewMultiFace(faces...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// Metrics returns the metrics for this Face.
|
||||
Metrics() Metrics
|
||||
@ -59,15 +59,15 @@ type Metrics struct {
|
||||
HDescent float64
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ func TestGlyphIndex(t *testing.T) {
|
||||
const sampleText = `The quick brown fox jumps
|
||||
over the lazy dog.`
|
||||
|
||||
f := text.NewStdFace(bitmapfont.Face)
|
||||
f := text.NewGoXFace(bitmapfont.Face)
|
||||
got := sampleText
|
||||
for _, g := range text.AppendGlyphs(nil, sampleText, f, nil) {
|
||||
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.GeoM.Translate(0, 0)
|
||||
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()
|
||||
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) {
|
||||
dr = image.Rect(0, 0, testStdFaceSize, testStdFaceSize)
|
||||
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, testGoXFaceSize, testGoXFaceSize)
|
||||
a := image.NewAlpha(dr)
|
||||
switch r {
|
||||
case 'a':
|
||||
@ -99,56 +99,56 @@ func (f *testStdFace) Glyph(dot fixed.Point26_6, r rune) (dr image.Rectangle, ma
|
||||
}
|
||||
}
|
||||
mask = a
|
||||
advance = fixed.I(testStdFaceSize)
|
||||
advance = fixed.I(testGoXFaceSize)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (f *testStdFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
bounds = fixed.R(0, 0, testStdFaceSize, testStdFaceSize)
|
||||
advance = fixed.I(testStdFaceSize)
|
||||
func (f *testGoXFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
bounds = fixed.R(0, 0, testGoXFaceSize, testGoXFaceSize)
|
||||
advance = fixed.I(testGoXFaceSize)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (f *testStdFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
return fixed.I(testStdFaceSize), true
|
||||
func (f *testGoXFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
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' {
|
||||
return fixed.I(-testStdFaceSize)
|
||||
return fixed.I(-testGoXFaceSize)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f *testStdFace) Close() error {
|
||||
func (f *testGoXFace) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *testStdFace) Metrics() font.Metrics {
|
||||
func (f *testGoXFace) Metrics() font.Metrics {
|
||||
return font.Metrics{
|
||||
Height: fixed.I(testStdFaceSize),
|
||||
Height: fixed.I(testGoXFaceSize),
|
||||
Ascent: 0,
|
||||
Descent: fixed.I(testStdFaceSize),
|
||||
Descent: fixed.I(testGoXFaceSize),
|
||||
XHeight: 0,
|
||||
CapHeight: fixed.I(testStdFaceSize),
|
||||
CapHeight: fixed.I(testGoXFaceSize),
|
||||
CaretSlope: image.Pt(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Issue #1378
|
||||
func TestNegativeKern(t *testing.T) {
|
||||
f := text.NewStdFace(&testStdFace{})
|
||||
dst := ebiten.NewImage(testStdFaceSize*2, testStdFaceSize)
|
||||
f := text.NewGoXFace(&testGoXFace{})
|
||||
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.
|
||||
op := &text.DrawOptions{}
|
||||
op.GeoM.Translate(0, 0)
|
||||
text.Draw(dst, "ab", f, op)
|
||||
for j := 0; j < testStdFaceSize; j++ {
|
||||
for i := 0; i < testStdFaceSize; i++ {
|
||||
for j := 0; j < testGoXFaceSize; j++ {
|
||||
for i := 0; i < testGoXFaceSize; i++ {
|
||||
got := dst.At(i, j)
|
||||
want := color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}
|
||||
if got != want {
|
||||
@ -159,10 +159,10 @@ func TestNegativeKern(t *testing.T) {
|
||||
|
||||
// The glyph 'a' should be treated correctly.
|
||||
op = &text.DrawOptions{}
|
||||
op.GeoM.Translate(testStdFaceSize, 0)
|
||||
op.GeoM.Translate(testGoXFaceSize, 0)
|
||||
text.Draw(dst, "a", f, op)
|
||||
for j := 0; j < testStdFaceSize; j++ {
|
||||
for i := testStdFaceSize; i < testStdFaceSize*2; i++ {
|
||||
for j := 0; j < testGoXFaceSize; j++ {
|
||||
for i := testGoXFaceSize; i < testGoXFaceSize*2; i++ {
|
||||
got := dst.At(i, j)
|
||||
want := color.RGBA{R: 0x80, G: 0x80, B: 0x80, A: 0x80}
|
||||
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) {
|
||||
dr = image.Rect(0, 0, unhashableStdFaceSize, unhashableStdFaceSize)
|
||||
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, unhashableGoXFaceSize, unhashableGoXFaceSize)
|
||||
a := image.NewAlpha(dr)
|
||||
for j := dr.Min.Y; j < dr.Max.Y; j++ {
|
||||
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
|
||||
advance = fixed.I(unhashableStdFaceSize)
|
||||
advance = fixed.I(unhashableGoXFaceSize)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (u *unhashableStdFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
bounds = fixed.R(0, 0, unhashableStdFaceSize, unhashableStdFaceSize)
|
||||
advance = fixed.I(unhashableStdFaceSize)
|
||||
func (u *unhashableGoXFace) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
||||
bounds = fixed.R(0, 0, unhashableGoXFaceSize, unhashableGoXFaceSize)
|
||||
advance = fixed.I(unhashableGoXFaceSize)
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
func (u *unhashableStdFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
return fixed.I(unhashableStdFaceSize), true
|
||||
func (u *unhashableGoXFace) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
||||
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
|
||||
}
|
||||
|
||||
func (u *unhashableStdFace) Close() error {
|
||||
func (u *unhashableGoXFace) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *unhashableStdFace) Metrics() font.Metrics {
|
||||
func (u *unhashableGoXFace) Metrics() font.Metrics {
|
||||
return font.Metrics{
|
||||
Height: fixed.I(unhashableStdFaceSize),
|
||||
Height: fixed.I(unhashableGoXFaceSize),
|
||||
Ascent: 0,
|
||||
Descent: fixed.I(unhashableStdFaceSize),
|
||||
Descent: fixed.I(unhashableGoXFaceSize),
|
||||
XHeight: 0,
|
||||
CapHeight: fixed.I(unhashableStdFaceSize),
|
||||
CapHeight: fixed.I(unhashableGoXFaceSize),
|
||||
CaretSlope: image.Pt(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Issue #2669
|
||||
func TestUnhashableFace(t *testing.T) {
|
||||
var face unhashableStdFace
|
||||
f := text.NewStdFace(&face)
|
||||
dst := ebiten.NewImage(unhashableStdFaceSize*2, unhashableStdFaceSize*2)
|
||||
var face unhashableGoXFace
|
||||
f := text.NewGoXFace(&face)
|
||||
dst := ebiten.NewImage(unhashableGoXFaceSize*2, unhashableGoXFaceSize*2)
|
||||
text.Draw(dst, "a", f, nil)
|
||||
|
||||
for j := 0; j < unhashableStdFaceSize*2; j++ {
|
||||
for i := 0; i < unhashableStdFaceSize*2; i++ {
|
||||
for j := 0; j < unhashableGoXFaceSize*2; j++ {
|
||||
for i := 0; i < unhashableGoXFaceSize*2; i++ {
|
||||
got := dst.At(i, j)
|
||||
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}
|
||||
}
|
||||
if got != want {
|
||||
|
Loading…
Reference in New Issue
Block a user