Added Resources abstraction.
This commit is contained in:
parent
a0660a9650
commit
b28b3e1838
76
addons/fs/afero.go
Normal file
76
addons/fs/afero.go
Normal file
@ -0,0 +1,76 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"opslag.de/schobers/zntg"
|
||||
"opslag.de/schobers/zntg/ui"
|
||||
)
|
||||
|
||||
type aferoResources struct {
|
||||
dir string
|
||||
fs afero.Fs
|
||||
copy *zntg.Dir
|
||||
}
|
||||
|
||||
var _ ui.Resources = &aferoResources{}
|
||||
|
||||
// NewAferoResources provides resources from a afero file system. The prefix is used as a prefix of the temporary directory.
|
||||
func NewAferoResources(fs afero.Fs, prefix string) (ui.Resources, error) {
|
||||
return NewAferoFallbackResources("", fs, prefix)
|
||||
}
|
||||
|
||||
// NewAferoFallbackResources provides resources from the directory first and uses afero file system as a fallback if the resource in the directory doesn't exist. The prefix is used as a prefix of the temporary directory.
|
||||
func NewAferoFallbackResources(dir string, fs afero.Fs, prefix string) (ui.Resources, error) {
|
||||
copy, err := zntg.NewTempDir(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &aferoResources{dir, fs, copy}, nil
|
||||
}
|
||||
|
||||
func (r *aferoResources) fetchAferoResource(name string) (string, error) {
|
||||
path := r.copy.FilePath(name)
|
||||
if !zntg.FileExists(path) {
|
||||
src, err := r.fs.Open(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
err = r.copy.Write(name, src)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (r *aferoResources) openAferoResource(name string) (io.ReadCloser, error) { return r.fs.Open(name) }
|
||||
|
||||
func (r *aferoResources) Destroy() error { return r.copy.Destroy() }
|
||||
|
||||
func (r *aferoResources) FetchResource(name string) (string, error) {
|
||||
if r.dir == "" {
|
||||
return r.fetchAferoResource(name)
|
||||
}
|
||||
path := filepath.Join(r.dir, name)
|
||||
if zntg.FileExists(path) {
|
||||
return path, nil
|
||||
}
|
||||
return r.fetchAferoResource(name)
|
||||
}
|
||||
|
||||
func (r *aferoResources) OpenResource(name string) (io.ReadCloser, error) {
|
||||
if r.dir == "" {
|
||||
return r.openAferoResource(name)
|
||||
}
|
||||
path := filepath.Join(r.dir, name)
|
||||
if zntg.FileExists(path) {
|
||||
return os.Open(path)
|
||||
}
|
||||
return r.openAferoResource(name)
|
||||
}
|
@ -47,7 +47,7 @@ func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
||||
})
|
||||
clean = nil
|
||||
|
||||
return &Renderer{disp, eq, nil, map[string]*font{}, user, ui.KeyState{}, ui.KeyModifierNone, ui.MouseCursorDefault}, nil
|
||||
return &Renderer{disp, eq, nil, map[string]*font{}, user, &ui.OSResources{}, ui.KeyState{}, ui.KeyModifierNone, ui.MouseCursorDefault}, nil
|
||||
}
|
||||
|
||||
// Renderer implements ui.Renderer using Allegro 5.
|
||||
@ -57,6 +57,7 @@ type Renderer struct {
|
||||
unh func(allg5.Event)
|
||||
ft map[string]*font
|
||||
user *allg5.UserEventSource
|
||||
res ui.Resources
|
||||
|
||||
keys ui.KeyState
|
||||
modifiers ui.KeyModifier
|
||||
@ -153,6 +154,7 @@ func (r *Renderer) Destroy() error {
|
||||
}
|
||||
r.ft = nil
|
||||
r.disp.Destroy()
|
||||
r.res.Destroy()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -182,10 +184,14 @@ func (r *Renderer) CreateTexture(source ui.ImageSource) (ui.Texture, error) {
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateTextureGo(im image.Image, source bool) (ui.Texture, error) {
|
||||
return r.createTexture(ui.ImageGoSource{im}, true)
|
||||
return r.createTexture(ui.ImageSourceGo{im}, true)
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateTexturePath(path string, source bool) (ui.Texture, error) {
|
||||
path, err := r.res.FetchResource(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bmp, err := allg5.LoadBitmap(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -250,7 +256,11 @@ func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness fl
|
||||
}
|
||||
|
||||
func (r *Renderer) RegisterFont(name, path string, size int) error {
|
||||
var f, err = allg5.LoadTTFFont(path, size)
|
||||
path, err := r.res.FetchResource(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
font, err := allg5.LoadTTFFont(path, size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -258,7 +268,7 @@ func (r *Renderer) RegisterFont(name, path string, size int) error {
|
||||
if prev != nil {
|
||||
prev.Destroy()
|
||||
}
|
||||
r.ft[name] = newFont(f)
|
||||
r.ft[name] = newFont(font)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -281,6 +291,8 @@ func (r *Renderer) RenderToDisplay() {
|
||||
r.disp.SetAsTarget()
|
||||
}
|
||||
|
||||
func (r *Renderer) Resources() ui.Resources { return r.res }
|
||||
|
||||
func (r *Renderer) Size() geom.PointF32 {
|
||||
return geom.PtF32(float32(r.disp.Width()), float32(r.disp.Height()))
|
||||
}
|
||||
@ -294,6 +306,13 @@ func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
|
||||
r.cursor = c
|
||||
}
|
||||
|
||||
func (r *Renderer) SetResourceProvider(factory func() ui.Resources) {
|
||||
if r.res != nil {
|
||||
r.res.Destroy()
|
||||
}
|
||||
r.res = factory()
|
||||
}
|
||||
|
||||
func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) {
|
||||
r.unh = handler
|
||||
}
|
||||
|
49
io.go
49
io.go
@ -7,6 +7,7 @@ import (
|
||||
_ "image/jpeg" // decoding of JPEG
|
||||
"image/png"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@ -47,6 +48,29 @@ func DecodeJSON(path string, value interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Dir is a convenience struct for representing a path to a directory.
|
||||
type Dir struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// FilePath returns the path of a file with the specified name in the directory.
|
||||
func (d *Dir) FilePath(name string) string {
|
||||
return filepath.Join(d.Path, name)
|
||||
}
|
||||
|
||||
// Write writes the content of the reader into a file with the specified name.
|
||||
func (d *Dir) Write(name string, r io.Reader) error {
|
||||
path := d.FilePath(name)
|
||||
dir := filepath.Dir(path)
|
||||
os.MkdirAll(dir, 0777)
|
||||
return EncodeFile(path, r, CopyReader)
|
||||
}
|
||||
|
||||
// Destroy removes the directory.
|
||||
func (d *Dir) Destroy() error {
|
||||
return os.RemoveAll(d.Path)
|
||||
}
|
||||
|
||||
// EncoderFn describes a generic encoder.
|
||||
type EncoderFn func(io.Writer, interface{}) error
|
||||
|
||||
@ -70,6 +94,14 @@ func EncodePNG(path string, im image.Image) error {
|
||||
return EncodeFile(path, im, PNGEncoder)
|
||||
}
|
||||
|
||||
// FileExists returns if file exists on specified path.
|
||||
func FileExists(path string) bool {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var _ DecoderFn = ImageDecoder
|
||||
|
||||
// ImageDecoder is a generic image decoder.
|
||||
@ -91,6 +123,15 @@ var _ EncoderFn = PNGEncoder
|
||||
// PNGEncoder is a generic PNG encoder.
|
||||
func PNGEncoder(w io.Writer, value interface{}) error { return png.Encode(w, value.(image.Image)) }
|
||||
|
||||
// NewTempDir creates a temporary directory.
|
||||
func NewTempDir(prefix string) (*Dir, error) {
|
||||
path, err := ioutil.TempDir("", prefix)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
return &Dir{path}, nil
|
||||
}
|
||||
|
||||
// UserDir gives back the user configuration directory with given name.
|
||||
func UserDir(name string) (string, error) {
|
||||
config, err := os.UserConfigDir()
|
||||
@ -113,3 +154,11 @@ func UserFile(app, name string) (string, error) {
|
||||
}
|
||||
return filepath.Join(dir, name), nil
|
||||
}
|
||||
|
||||
var _ EncoderFn = CopyReader
|
||||
|
||||
// CopyReader copies the provided value to the output.
|
||||
func CopyReader(w io.Writer, value interface{}) error {
|
||||
_, err := io.Copy(w, value.(io.Reader))
|
||||
return err
|
||||
}
|
||||
|
@ -19,10 +19,11 @@ import (
|
||||
var errNotImplemented = errors.New(`not implemented`)
|
||||
|
||||
type Renderer struct {
|
||||
window *sdl.Window
|
||||
renderer *sdl.Renderer
|
||||
refresh uint32
|
||||
fonts map[string]*Font
|
||||
window *sdl.Window
|
||||
renderer *sdl.Renderer
|
||||
refresh uint32
|
||||
fonts map[string]*Font
|
||||
resources ui.Resources
|
||||
|
||||
mouse geom.PointF32
|
||||
cursor ui.MouseCursor
|
||||
@ -84,11 +85,12 @@ func NewRenderer(title string, width, height int32, opts NewRendererOptions) (*R
|
||||
clean = nil
|
||||
|
||||
return &Renderer{
|
||||
window: window,
|
||||
renderer: renderer,
|
||||
refresh: refresh,
|
||||
fonts: map[string]*Font{},
|
||||
cursors: map[sdl.SystemCursor]*sdl.Cursor{},
|
||||
window: window,
|
||||
renderer: renderer,
|
||||
refresh: refresh,
|
||||
fonts: map[string]*Font{},
|
||||
resources: &ui.OSResources{},
|
||||
cursors: map[sdl.SystemCursor]*sdl.Cursor{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -210,6 +212,7 @@ func (r *Renderer) Destroy() error {
|
||||
r.window.Destroy()
|
||||
ttf.Quit()
|
||||
sdl.Quit()
|
||||
r.resources.Destroy()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -253,11 +256,11 @@ func (r *Renderer) CreateTexture(source ui.ImageSource) (ui.Texture, error) {
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateTextureGo(m image.Image, source bool) (ui.Texture, error) {
|
||||
return r.createTexture(ui.ImageGoSource{Image: m}, source)
|
||||
return r.createTexture(ui.ImageSourceGo{Image: m}, source)
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateTexturePath(path string, source bool) (ui.Texture, error) {
|
||||
return r.createTexture(ui.ImageFileSource(path), source)
|
||||
return r.createTexture(ui.ImageSourceResource{Resources: r.resources, Name: path}, source)
|
||||
}
|
||||
|
||||
func (r *Renderer) CreateTextureTarget(w, h float32) (ui.Texture, error) {
|
||||
@ -331,6 +334,10 @@ func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness fl
|
||||
}
|
||||
|
||||
func (r *Renderer) RegisterFont(name, path string, size int) error {
|
||||
path, err := r.resources.FetchResource(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
font, err := ttf.OpenFont(path, size)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -420,6 +427,17 @@ func (r *Renderer) TextAlign(p geom.PointF32, font string, color color.Color, te
|
||||
}
|
||||
}
|
||||
|
||||
// Resources
|
||||
|
||||
func (r *Renderer) Resources() ui.Resources { return r.resources }
|
||||
|
||||
func (r *Renderer) SetResourceProvider(factory func() ui.Resources) {
|
||||
if r.resources != nil {
|
||||
r.resources.Destroy()
|
||||
}
|
||||
r.resources = factory()
|
||||
}
|
||||
|
||||
// Texture
|
||||
|
||||
func (r *Renderer) Image() image.Image { return nil }
|
||||
|
@ -3,10 +3,10 @@ package ui
|
||||
type Context interface {
|
||||
Animate()
|
||||
HasQuit() bool
|
||||
Textures() *Textures
|
||||
Quit()
|
||||
Renderer() Renderer
|
||||
Style() *Style
|
||||
Textures() *Textures
|
||||
}
|
||||
|
||||
var _ Context = &context{}
|
||||
@ -32,7 +32,9 @@ func (c *context) HasQuit() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Textures() *Textures { return c.textures }
|
||||
func (c *context) Renderer() Renderer { return c.r }
|
||||
|
||||
func (c *context) Style() *Style { return c.style }
|
||||
|
||||
func (c *context) Quit() {
|
||||
if !c.HasQuit() {
|
||||
@ -40,9 +42,7 @@ func (c *context) Quit() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *context) Renderer() Renderer { return c.r }
|
||||
|
||||
func (c *context) Style() *Style { return c.style }
|
||||
func (c *context) Textures() *Textures { return c.textures }
|
||||
|
||||
// Handle implement EventTarget
|
||||
|
||||
|
56
ui/copyresources.go
Normal file
56
ui/copyresources.go
Normal file
@ -0,0 +1,56 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"opslag.de/schobers/zntg"
|
||||
)
|
||||
|
||||
var _ Resources = &CopyResources{}
|
||||
|
||||
// CopyResources copies and opens resources to a temporary directory.
|
||||
type CopyResources struct {
|
||||
Source Resources
|
||||
|
||||
copy *zntg.Dir
|
||||
}
|
||||
|
||||
// NewCopyResource creates a proxy that copied resources first to disk.
|
||||
func NewCopyResource(prefix string, source Resources) (*CopyResources, error) {
|
||||
copy, err := zntg.NewTempDir(prefix)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
return &CopyResources{source, copy}, nil
|
||||
}
|
||||
|
||||
// FetchResource copies the file from the source to disk and returns the path to it.
|
||||
func (r *CopyResources) FetchResource(name string) (string, error) {
|
||||
path := r.copy.FilePath(name)
|
||||
if !zntg.FileExists(path) {
|
||||
src, err := r.Source.OpenResource(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
err = r.copy.Write(name, src)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
|
||||
// OpenResource opens the (copied) resource on disk.
|
||||
func (r *CopyResources) OpenResource(name string) (io.ReadCloser, error) {
|
||||
path := r.copy.FilePath(name)
|
||||
src, err := os.Open(path)
|
||||
return src, err
|
||||
}
|
||||
|
||||
// Destroy destroy the copy of the resources.
|
||||
func (r *CopyResources) Destroy() error {
|
||||
return r.copy.Destroy()
|
||||
}
|
@ -2,36 +2,46 @@ package ui
|
||||
|
||||
import (
|
||||
"image"
|
||||
"os"
|
||||
|
||||
"opslag.de/schobers/zntg"
|
||||
)
|
||||
|
||||
type ImageSource interface {
|
||||
CreateImage() (image.Image, error)
|
||||
}
|
||||
|
||||
type ImageFileSource string
|
||||
type ImageSourceFile string
|
||||
|
||||
var _ ImageSource = ImageFileSource("")
|
||||
var _ ImageSource = ImageSourceFile("")
|
||||
|
||||
func (s ImageFileSource) CreateImage() (image.Image, error) {
|
||||
f, err := os.Open(string(s))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
m, _, err := image.Decode(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
func (s ImageSourceFile) CreateImage() (image.Image, error) {
|
||||
return zntg.DecodeImage(string(s))
|
||||
}
|
||||
|
||||
type ImageGoSource struct {
|
||||
type ImageSourceGo struct {
|
||||
image.Image
|
||||
}
|
||||
|
||||
var _ ImageSource = ImageGoSource{}
|
||||
var _ ImageSource = ImageSourceGo{}
|
||||
|
||||
func (s ImageGoSource) CreateImage() (image.Image, error) {
|
||||
func (s ImageSourceGo) CreateImage() (image.Image, error) {
|
||||
return s.Image, nil
|
||||
}
|
||||
|
||||
type ImageSourceResource struct {
|
||||
Resources Resources
|
||||
Name string
|
||||
}
|
||||
|
||||
func (s ImageSourceResource) CreateImage() (image.Image, error) {
|
||||
src, err := s.Resources.OpenResource(s.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer src.Close()
|
||||
value, err := zntg.ImageDecoder(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value.(image.Image), nil
|
||||
}
|
||||
|
34
ui/osresources.go
Normal file
34
ui/osresources.go
Normal file
@ -0,0 +1,34 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
var _ Resources = &OSResources{}
|
||||
|
||||
// DefaultResources returns the default Resources implementation (OSResources).
|
||||
func DefaultResources() Resources {
|
||||
return &OSResources{}
|
||||
}
|
||||
|
||||
// OSResources is Resources implementation that uses the default file system directly.
|
||||
type OSResources struct {
|
||||
}
|
||||
|
||||
// FetchResource checks if file is available and returns the specified path.
|
||||
func (r *OSResources) FetchResource(name string) (string, error) {
|
||||
_, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, nil
|
||||
}
|
||||
|
||||
// OpenResource opens the specified file on disk.
|
||||
func (r *OSResources) OpenResource(name string) (io.ReadCloser, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
// Destroy does nothing.
|
||||
func (r *OSResources) Destroy() error { return nil }
|
@ -35,4 +35,8 @@ type Renderer interface {
|
||||
Target() Texture
|
||||
Text(p geom.PointF32, font string, color color.Color, text string)
|
||||
TextAlign(p geom.PointF32, font string, color color.Color, text string, align HorizontalAlignment)
|
||||
|
||||
// Resources
|
||||
Resources() Resources
|
||||
SetResourceProvider(factory func() Resources)
|
||||
}
|
||||
|
13
ui/resources.go
Normal file
13
ui/resources.go
Normal file
@ -0,0 +1,13 @@
|
||||
package ui
|
||||
|
||||
import "io"
|
||||
|
||||
// Resources is an abstraction on resources.
|
||||
type Resources interface {
|
||||
// FetchResource should fetch the resource with the specified name and return a path (on disk) where the resource can be accessed.
|
||||
FetchResource(name string) (string, error)
|
||||
// OpenResource should open the resource with the specified name. The user is responsible for closing the resource.
|
||||
OpenResource(name string) (io.ReadCloser, error)
|
||||
// Destroy can be used for cleaning up at the end of the applications lifetime.
|
||||
Destroy() error
|
||||
}
|
Loading…
Reference in New Issue
Block a user