mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-12 22:17:26 +01:00
parent
40aa35750c
commit
8c25fac860
94
examples/dropfile/main.go
Normal file
94
examples/dropfile/main.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2023 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
images []*ebiten.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Update() error {
|
||||||
|
for _, f := range ebiten.AppendDroppedFiles(nil) {
|
||||||
|
// Calling Close is not mandatory, but it is sligtly good to save memory.
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("Name: %s, Size: %d, IsDir: %t, ModTime: %v", fi.Name(), fi.Size(), fi.IsDir(), fi.ModTime())
|
||||||
|
|
||||||
|
img, err := png.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("decoding PNG failed: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
g.images = append(g.images, ebiten.NewImageFromImage(img))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
|
if len(g.images) == 0 {
|
||||||
|
ebitenutil.DebugPrint(screen, "Drop PNG files onto this window!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageSize = 128
|
||||||
|
xcount := screen.Bounds().Dx() / imageSize
|
||||||
|
if xcount == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, img := range g.images {
|
||||||
|
x := (i % xcount) * imageSize
|
||||||
|
y := (i / xcount) * imageSize
|
||||||
|
|
||||||
|
s := imageSize / float64(img.Bounds().Dx())
|
||||||
|
if sy := imageSize / float64(img.Bounds().Dy()); s > sy {
|
||||||
|
s = sy
|
||||||
|
}
|
||||||
|
if s > 1 {
|
||||||
|
s = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
op := &ebiten.DrawImageOptions{}
|
||||||
|
op.GeoM.Scale(s, s)
|
||||||
|
op.GeoM.Translate(float64(x), float64(y))
|
||||||
|
op.Filter = ebiten.FilterLinear
|
||||||
|
screen.DrawImage(img, op)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||||
|
return outsideWidth, outsideHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ebiten.SetWindowResizable(true)
|
||||||
|
ebiten.SetWindowTitle("Dropping Files (Ebitengine Demo)")
|
||||||
|
if err := ebiten.RunGame(&Game{}); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
12
input.go
12
input.go
@ -15,6 +15,7 @@
|
|||||||
package ebiten
|
package ebiten
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
"github.com/hajimehoshi/ebiten/v2/internal/gamepad"
|
||||||
@ -22,7 +23,7 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
"github.com/hajimehoshi/ebiten/v2/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppendInputChars appends "printable" runes, read from the keyboard at the time update is called, to runes,
|
// AppendInputChars appends "printable" runes, read from the keyboard at the time Update is called, to runes,
|
||||||
// and returns the extended buffer.
|
// and returns the extended buffer.
|
||||||
// Giving a slice that already has enough capacity works efficiently.
|
// Giving a slice that already has enough capacity works efficiently.
|
||||||
//
|
//
|
||||||
@ -40,7 +41,7 @@ func AppendInputChars(runes []rune) []rune {
|
|||||||
return theInputState.appendInputChars(runes)
|
return theInputState.appendInputChars(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// InputChars return "printable" runes read from the keyboard at the time update is called.
|
// InputChars return "printable" runes read from the keyboard at the time Update is called.
|
||||||
//
|
//
|
||||||
// Deprecated: as of v2.2. Use AppendInputChars instead.
|
// Deprecated: as of v2.2. Use AppendInputChars instead.
|
||||||
func InputChars() []rune {
|
func InputChars() []rune {
|
||||||
@ -474,6 +475,11 @@ func (i *inputState) touchPosition(id TouchID) (int, int) {
|
|||||||
func (i *inputState) windowBeingClosed() bool {
|
func (i *inputState) windowBeingClosed() bool {
|
||||||
i.m.Lock()
|
i.m.Lock()
|
||||||
defer i.m.Unlock()
|
defer i.m.Unlock()
|
||||||
|
|
||||||
return i.state.WindowBeingClosed
|
return i.state.WindowBeingClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *inputState) appendDroppedFiles(files []fs.File) []fs.File {
|
||||||
|
i.m.Lock()
|
||||||
|
defer i.m.Unlock()
|
||||||
|
return append(files, i.state.DroppedFiles...)
|
||||||
|
}
|
||||||
|
@ -38,6 +38,15 @@ func ToCloseCallback(cb func(window *Window)) CloseCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToDropCallback(cb func(window *Window, names []string)) DropCallback {
|
||||||
|
if cb == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func(window *glfw.Window, names []string) {
|
||||||
|
cb(theWindows.get(window), names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ToFramebufferSizeCallback(cb func(window *Window, width int, height int)) FramebufferSizeCallback {
|
func ToFramebufferSizeCallback(cb func(window *Window, width int, height int)) FramebufferSizeCallback {
|
||||||
if cb == nil {
|
if cb == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -36,6 +36,15 @@ func ToCloseCallback(cb func(window *Window)) CloseCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToDropCallback(cb func(window *Window, names []string)) DropCallback {
|
||||||
|
if cb == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return func(window *goglfw.Window, names []string) {
|
||||||
|
cb((*Window)(window), names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ToFramebufferSizeCallback(cb func(window *Window, width int, height int)) FramebufferSizeCallback {
|
func ToFramebufferSizeCallback(cb func(window *Window, width int, height int)) FramebufferSizeCallback {
|
||||||
if cb == nil {
|
if cb == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -187,6 +187,11 @@ func (w *Window) SetCloseCallback(cbfun CloseCallback) (previous CloseCallback)
|
|||||||
return ToCloseCallback(nil) // TODO
|
return ToCloseCallback(nil) // TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Window) SetDropCallback(cbfun DropCallback) (previous DropCallback) {
|
||||||
|
w.w.SetDropCallback(cbfun)
|
||||||
|
return ToDropCallback(nil) // TODO
|
||||||
|
}
|
||||||
|
|
||||||
func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (previous FramebufferSizeCallback) {
|
func (w *Window) SetFramebufferSizeCallback(cbfun FramebufferSizeCallback) (previous FramebufferSizeCallback) {
|
||||||
w.w.SetFramebufferSizeCallback(cbfun)
|
w.w.SetFramebufferSizeCallback(cbfun)
|
||||||
return ToFramebufferSizeCallback(nil) // TODO
|
return ToFramebufferSizeCallback(nil) // TODO
|
||||||
|
@ -184,6 +184,14 @@ func (w *Window) SetCloseCallback(cbfun CloseCallback) (previous CloseCallback)
|
|||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Window) SetDropCallback(cbfun DropCallback) (previous DropCallback) {
|
||||||
|
f, err := (*goglfw.Window)(w).SetDropCallback(cbfun)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
func (w *Window) SetCursor(cursor *Cursor) {
|
func (w *Window) SetCursor(cursor *Cursor) {
|
||||||
if err := (*goglfw.Window)(w).SetCursor((*goglfw.Cursor)(cursor)); err != nil {
|
if err := (*goglfw.Window)(w).SetCursor((*goglfw.Cursor)(cursor)); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
type (
|
type (
|
||||||
CharModsCallback = glfw.CharModsCallback
|
CharModsCallback = glfw.CharModsCallback
|
||||||
CloseCallback = glfw.CloseCallback
|
CloseCallback = glfw.CloseCallback
|
||||||
|
DropCallback = glfw.DropCallback
|
||||||
FramebufferSizeCallback = glfw.FramebufferSizeCallback
|
FramebufferSizeCallback = glfw.FramebufferSizeCallback
|
||||||
MonitorCallback = glfw.MonitorCallback
|
MonitorCallback = glfw.MonitorCallback
|
||||||
ScrollCallback = glfw.ScrollCallback
|
ScrollCallback = glfw.ScrollCallback
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
type (
|
type (
|
||||||
CharModsCallback = goglfw.CharModsCallback
|
CharModsCallback = goglfw.CharModsCallback
|
||||||
CloseCallback = goglfw.CloseCallback
|
CloseCallback = goglfw.CloseCallback
|
||||||
|
DropCallback = goglfw.DropCallback
|
||||||
FramebufferSizeCallback = goglfw.FramebufferSizeCallback
|
FramebufferSizeCallback = goglfw.FramebufferSizeCallback
|
||||||
MonitorCallback = goglfw.MonitorCallback
|
MonitorCallback = goglfw.MonitorCallback
|
||||||
ScrollCallback = goglfw.ScrollCallback
|
ScrollCallback = goglfw.ScrollCallback
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/fs"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ type InputState struct {
|
|||||||
Touches []Touch
|
Touches []Touch
|
||||||
Runes []rune
|
Runes []rune
|
||||||
WindowBeingClosed bool
|
WindowBeingClosed bool
|
||||||
|
DroppedFiles []fs.File
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InputState) copyAndReset(dst *InputState) {
|
func (i *InputState) copyAndReset(dst *InputState) {
|
||||||
@ -59,14 +61,22 @@ func (i *InputState) copyAndReset(dst *InputState) {
|
|||||||
dst.Touches = append(dst.Touches[:0], i.Touches...)
|
dst.Touches = append(dst.Touches[:0], i.Touches...)
|
||||||
dst.Runes = append(dst.Runes[:0], i.Runes...)
|
dst.Runes = append(dst.Runes[:0], i.Runes...)
|
||||||
dst.WindowBeingClosed = i.WindowBeingClosed
|
dst.WindowBeingClosed = i.WindowBeingClosed
|
||||||
|
for idx := range dst.DroppedFiles {
|
||||||
|
dst.DroppedFiles[idx] = nil
|
||||||
|
}
|
||||||
|
dst.DroppedFiles = append(dst.DroppedFiles[:0], i.DroppedFiles...)
|
||||||
|
|
||||||
// Reset the members that are updated by deltas, rather than absolute values.
|
// Reset the members that are updated by deltas, rather than absolute values.
|
||||||
i.WheelX = 0
|
i.WheelX = 0
|
||||||
i.WheelY = 0
|
i.WheelY = 0
|
||||||
i.Runes = i.Runes[:0]
|
i.Runes = i.Runes[:0]
|
||||||
|
|
||||||
// Reset the member that is never reset until it is explicitly done.
|
// Reset the members that are never reset until they are explicitly done.
|
||||||
i.WindowBeingClosed = false
|
i.WindowBeingClosed = false
|
||||||
|
for idx := range i.DroppedFiles {
|
||||||
|
i.DroppedFiles[idx] = nil
|
||||||
|
}
|
||||||
|
i.DroppedFiles = i.DroppedFiles[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InputState) appendRune(r rune) {
|
func (i *InputState) appendRune(r rune) {
|
||||||
@ -75,3 +85,7 @@ func (i *InputState) appendRune(r rune) {
|
|||||||
}
|
}
|
||||||
i.Runes = append(i.Runes, r)
|
i.Runes = append(i.Runes, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *InputState) appendDroppedFiles(files []fs.File) {
|
||||||
|
i.DroppedFiles = append(i.DroppedFiles, files...)
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
@ -105,6 +106,7 @@ type userInterfaceImpl struct {
|
|||||||
closeCallback glfw.CloseCallback
|
closeCallback glfw.CloseCallback
|
||||||
framebufferSizeCallback glfw.FramebufferSizeCallback
|
framebufferSizeCallback glfw.FramebufferSizeCallback
|
||||||
defaultFramebufferSizeCallback glfw.FramebufferSizeCallback
|
defaultFramebufferSizeCallback glfw.FramebufferSizeCallback
|
||||||
|
dropCallback glfw.DropCallback
|
||||||
framebufferSizeCallbackCh chan struct{}
|
framebufferSizeCallbackCh chan struct{}
|
||||||
|
|
||||||
glContextSetOnce sync.Once
|
glContextSetOnce sync.Once
|
||||||
@ -746,6 +748,30 @@ func (u *userInterfaceImpl) registerWindowFramebufferSizeCallback() {
|
|||||||
u.window.SetFramebufferSizeCallback(u.defaultFramebufferSizeCallback)
|
u.window.SetFramebufferSizeCallback(u.defaultFramebufferSizeCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *userInterfaceImpl) registerDropCallback() {
|
||||||
|
if u.dropCallback == nil {
|
||||||
|
u.dropCallback = glfw.ToDropCallback(func(_ *glfw.Window, names []string) {
|
||||||
|
var files []fs.File
|
||||||
|
for _, name := range names {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
files = append(files, &errorFile{
|
||||||
|
name: name,
|
||||||
|
err: err,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.m.Lock()
|
||||||
|
defer u.m.Unlock()
|
||||||
|
u.inputState.appendDroppedFiles(files)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
u.window.SetDropCallback(u.dropCallback)
|
||||||
|
}
|
||||||
|
|
||||||
// waitForFramebufferSizeCallback waits for GLFW's FramebufferSize callback.
|
// waitForFramebufferSizeCallback waits for GLFW's FramebufferSize callback.
|
||||||
// f is a process executed after registering the callback.
|
// f is a process executed after registering the callback.
|
||||||
// If the callback is not invoked for a while, waitForFramebufferSizeCallback times out and return.
|
// If the callback is not invoked for a while, waitForFramebufferSizeCallback times out and return.
|
||||||
@ -899,6 +925,7 @@ func (u *userInterfaceImpl) initOnMainThread(options *RunOptions) error {
|
|||||||
u.registerWindowCloseCallback()
|
u.registerWindowCloseCallback()
|
||||||
u.registerWindowFramebufferSizeCallback()
|
u.registerWindowFramebufferSizeCallback()
|
||||||
u.registerInputCallbacks()
|
u.registerInputCallbacks()
|
||||||
|
u.registerDropCallback()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1517,3 +1544,20 @@ func (u *userInterfaceImpl) setOrigWindowPos(x, y int) {
|
|||||||
func IsScreenTransparentAvailable() bool {
|
func IsScreenTransparentAvailable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type errorFile struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorFile) Stat() (fs.FileInfo, error) {
|
||||||
|
return nil, fmt.Errorf("ui: failed to open %s: %w", e.name, e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorFile) Read(buf []byte) (int, error) {
|
||||||
|
return 0, fmt.Errorf("ui: failed to open %s: %w", e.name, e.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errorFile) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
@ -622,6 +625,27 @@ func setCanvasEventHandlers(v js.Value) {
|
|||||||
window.Get("location").Call("reload")
|
window.Get("location").Call("reload")
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Drop
|
||||||
|
v.Call("addEventListener", "dragover", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
e := args[0]
|
||||||
|
e.Call("preventDefault")
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
v.Call("addEventListener", "drop", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
e := args[0]
|
||||||
|
e.Call("preventDefault")
|
||||||
|
data := e.Get("dataTransfer")
|
||||||
|
if !data.Truthy() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
files := data.Get("files")
|
||||||
|
for i := 0; i < files.Length(); i++ {
|
||||||
|
theUI.inputState.DroppedFiles = append(theUI.inputState.DroppedFiles, &file{v: files.Index(i)})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userInterfaceImpl) forceUpdateOnMinimumFPSMode() {
|
func (u *userInterfaceImpl) forceUpdateOnMinimumFPSMode() {
|
||||||
@ -696,3 +720,113 @@ func (u *userInterfaceImpl) updateIconIfNeeded() {
|
|||||||
func IsScreenTransparentAvailable() bool {
|
func IsScreenTransparentAvailable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
v js.Value
|
||||||
|
cursor int64
|
||||||
|
uint8Array js.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) Stat() (fs.FileInfo, error) {
|
||||||
|
return &fileInfo{v: f.v}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) Read(buf []byte) (int, error) {
|
||||||
|
if !f.uint8Array.Truthy() {
|
||||||
|
chArrayBuffer := make(chan js.Value, 1)
|
||||||
|
cbThen := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
chArrayBuffer <- args[0]
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
defer cbThen.Release()
|
||||||
|
|
||||||
|
chError := make(chan js.Value, 1)
|
||||||
|
cbCatch := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
chError <- args[0]
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
defer cbCatch.Release()
|
||||||
|
|
||||||
|
f.v.Call("arrayBuffer").Call("then", cbThen).Call("catch", cbCatch)
|
||||||
|
select {
|
||||||
|
case ab := <-chArrayBuffer:
|
||||||
|
f.uint8Array = js.Global().Get("Uint8Array").New(ab)
|
||||||
|
case err := <-chError:
|
||||||
|
return 0, fmt.Errorf("%s", err.Call("toString").String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size := int64(f.uint8Array.Get("byteLength").Float())
|
||||||
|
if f.cursor >= size {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slice := f.uint8Array.Call("subarray", f.cursor, f.cursor+int64(len(buf)))
|
||||||
|
n := slice.Get("byteLength").Int()
|
||||||
|
js.CopyBytesToGo(buf[:n], slice)
|
||||||
|
f.cursor += int64(n)
|
||||||
|
if f.cursor >= size {
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileInfo struct {
|
||||||
|
v js.Value
|
||||||
|
isDir bool
|
||||||
|
isDirOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) Name() string {
|
||||||
|
return f.v.Get("name").String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) Size() int64 {
|
||||||
|
return int64(f.v.Get("size").Float())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) Mode() fs.FileMode {
|
||||||
|
return 0400
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) ModTime() time.Time {
|
||||||
|
return time.UnixMilli(int64(f.v.Get("lastModified").Float()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) IsDir() bool {
|
||||||
|
f.isDirOnce.Do(func() {
|
||||||
|
ch := make(chan struct{})
|
||||||
|
|
||||||
|
cbThen := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
close(ch)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
defer cbThen.Release()
|
||||||
|
|
||||||
|
cbCatch := js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
|
// This is not a reliable way to check whether the file is a directory or not.
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/File
|
||||||
|
// TODO: Should the file system API be used?
|
||||||
|
f.isDir = true
|
||||||
|
close(ch)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
defer cbCatch.Release()
|
||||||
|
|
||||||
|
f.v.Call("arrayBuffer").Call("then", cbThen).Call("catch", cbCatch)
|
||||||
|
<-ch
|
||||||
|
})
|
||||||
|
return f.isDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fileInfo) Sys() any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
12
run.go
12
run.go
@ -18,6 +18,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io/fs"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
"github.com/hajimehoshi/ebiten/v2/internal/clock"
|
||||||
@ -664,3 +665,14 @@ func toUIRunOptions(options *RunGameOptions) *ui.RunOptions {
|
|||||||
SkipTaskbar: options.SkipTaskbar,
|
SkipTaskbar: options.SkipTaskbar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AppendDroppedFiles appends files, dropped at the time Update is called, to the given slice,
|
||||||
|
// and returns the extended buffer.
|
||||||
|
// Giving a slice that already has enough capacity works efficiently.
|
||||||
|
//
|
||||||
|
// AppendDroppedFiles works on desktops and browsers.
|
||||||
|
//
|
||||||
|
// AppendDroppedFiles is concurrent-safe.
|
||||||
|
func AppendDroppedFiles(files []fs.File) []fs.File {
|
||||||
|
return theInputState.appendDroppedFiles(files)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user