diff --git a/addons/aferores/afero.go b/addons/aferores/afero.go new file mode 100644 index 0000000..e9cd5a4 --- /dev/null +++ b/addons/aferores/afero.go @@ -0,0 +1,23 @@ +package aferores + +import ( + "io" + + "github.com/spf13/afero" + "opslag.de/schobers/zntg/ui" +) + +type aferoResources struct { + afero.Fs +} + +var _ ui.Resources = &aferoResources{} + +// New provides resources from a afero file system. +func New(fs afero.Fs) ui.Resources { + return &aferoResources{fs} +} + +func (r *aferoResources) Destroy() error { return nil } + +func (r *aferoResources) OpenResource(name string) (io.ReadCloser, error) { return r.Fs.Open(name) } diff --git a/addons/res/afero.go b/addons/res/afero.go deleted file mode 100644 index 237a86a..0000000 --- a/addons/res/afero.go +++ /dev/null @@ -1,76 +0,0 @@ -package res - -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) -} diff --git a/addons/riceres/rice.go b/addons/riceres/rice.go new file mode 100644 index 0000000..73bc770 --- /dev/null +++ b/addons/riceres/rice.go @@ -0,0 +1,23 @@ +package riceres + +import ( + "io" + + rice "github.com/GeertJohan/go.rice" + "opslag.de/schobers/zntg/ui" +) + +type riceResources struct { + *rice.Box +} + +var _ ui.Resources = &riceResources{} + +// New provides resources from a rice Box. +func New(box *rice.Box) ui.Resources { + return &riceResources{box} +} + +func (r *riceResources) Destroy() error { return nil } + +func (r *riceResources) OpenResource(name string) (io.ReadCloser, error) { return r.Box.Open(name) } diff --git a/allg5ui/renderer.go b/allg5ui/renderer.go index 3b9a43e..046f7a1 100644 --- a/allg5ui/renderer.go +++ b/allg5ui/renderer.go @@ -56,7 +56,7 @@ type Renderer struct { eq *allg5.EventQueue unh func(allg5.Event) user *allg5.UserEventSource - res ui.Resources + res ui.PhysicalResources dispPos geom.Point keys ui.KeyState @@ -335,7 +335,11 @@ func (r *Renderer) SetResourceProvider(factory func() ui.Resources) { if r.res != nil { r.res.Destroy() } - r.res = factory() + res, err := ui.NewCopyResources("allg5ui", factory(), false) + if err != nil { + return + } + r.res = res } func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) { diff --git a/sdlui/renderer.go b/sdlui/renderer.go index 8d39454..5d23c8e 100644 --- a/sdlui/renderer.go +++ b/sdlui/renderer.go @@ -22,7 +22,7 @@ type Renderer struct { window *sdl.Window renderer *sdl.Renderer refresh uint32 - resources ui.Resources + resources ui.PhysicalResources mouse geom.PointF32 cursor ui.MouseCursor @@ -477,7 +477,11 @@ func (r *Renderer) SetResourceProvider(factory func() ui.Resources) { if r.resources != nil { r.resources.Destroy() } - r.resources = factory() + resources, err := ui.NewCopyResources("sdlui", factory(), false) + if err != nil { + return + } + r.resources = resources } // Texture diff --git a/ui/copyresources.go b/ui/copyresources.go index 07e11a3..58deaa9 100644 --- a/ui/copyresources.go +++ b/ui/copyresources.go @@ -7,22 +7,28 @@ import ( "opslag.de/schobers/zntg" ) -var _ Resources = &CopyResources{} +var _ PhysicalResources = &CopyResources{} -// CopyResources copies and opens resources to a temporary directory. +// CopyResources copies the resource to a temporary directory when fetched. Optionally the resource is fetched as well before opening. type CopyResources struct { Source Resources copy *zntg.Dir + + mustCopyBeforeOpen bool } -// NewCopyResource creates a proxy that copied resources first to disk. -func NewCopyResource(prefix string, source Resources) (*CopyResources, error) { +func newCopyResources(prefix string, source Resources, mustCopyBeforeOpen bool) (*CopyResources, error) { copy, err := zntg.NewTempDir(prefix) if nil != err { return nil, err } - return &CopyResources{source, copy}, nil + return &CopyResources{source, copy, mustCopyBeforeOpen}, nil +} + +// NewCopyResources creates a proxy that copies resources first to disk. Copy on OpenResource only happens when mustCopyBeforeOpen is set to true. +func NewCopyResources(prefix string, source Resources, mustCopyBeforeOpen bool) (*CopyResources, error) { + return newCopyResources(prefix, source, false) } // FetchResource copies the file from the source to disk and returns the path to it. @@ -43,11 +49,16 @@ func (r *CopyResources) FetchResource(name string) (string, error) { return path, nil } -// OpenResource opens the (copied) resource on disk. +// OpenResource opens the (optionally copied) resource. func (r *CopyResources) OpenResource(name string) (io.ReadCloser, error) { - path := r.copy.FilePath(name) - src, err := os.Open(path) - return src, err + if r.mustCopyBeforeOpen { + path, err := r.FetchResource(name) + if err != nil { + return nil, err + } + return os.Open(path) + } + return r.Source.OpenResource(name) } // Destroy destroy the copy of the resources. diff --git a/ui/fallbackresources.go b/ui/fallbackresources.go new file mode 100644 index 0000000..cf8b83e --- /dev/null +++ b/ui/fallbackresources.go @@ -0,0 +1,34 @@ +package ui + +import "io" + +var _ Resources = &fallbackResources{} + +type fallbackResources struct { + resources Resources + fallback Resources +} + +// NewFallbackResources creates a Resources that first will try to access resources and on failure will try to access the fallback resources. Will take ownership of both resources (Destroy). +func NewFallbackResources(resources, fallback Resources) Resources { + return &fallbackResources{resources, fallback} +} + +func (r *fallbackResources) OpenResource(name string) (io.ReadCloser, error) { + if reader, err := r.resources.OpenResource(name); err == nil { + return reader, nil + } + return r.fallback.OpenResource(name) +} + +func (r *fallbackResources) Destroy() error { + errResources := r.resources.Destroy() + errFallback := r.fallback.Destroy() + if errResources != nil { + return errResources + } + if errFallback != nil { + return errFallback + } + return nil +} diff --git a/ui/osresources.go b/ui/osresources.go index 6572644..a4e48f1 100644 --- a/ui/osresources.go +++ b/ui/osresources.go @@ -5,10 +5,10 @@ import ( "os" ) -var _ Resources = &OSResources{} +var _ PhysicalResources = &OSResources{} // DefaultResources returns the default Resources implementation (OSResources). -func DefaultResources() Resources { +func DefaultResources() PhysicalResources { return &OSResources{} } diff --git a/ui/pathresources.go b/ui/pathresources.go new file mode 100644 index 0000000..19500f1 --- /dev/null +++ b/ui/pathresources.go @@ -0,0 +1,62 @@ +package ui + +import ( + "io" + "path/filepath" +) + +var _ Resources = &PathResources{} + +// PathResources implements Resources by adding a prefix to the requested source name before proxying it to its source. +type PathResources struct { + Source Resources + Prefix string +} + +// NewPathResources creates a new Resources by adding a prefix to the requested source name before proxying it to its source. If source is nil it will use the OS file system for its resources. +func NewPathResources(source Resources, prefix string) *PathResources { + if source == nil { + source = &OSResources{} + } + return &PathResources{source, prefix} +} + +// Destroy destroys the source. +func (r *PathResources) Destroy() error { return r.Source.Destroy() } + +// OpenResource opens the resource with the prefixed name. +func (r *PathResources) OpenResource(name string) (io.ReadCloser, error) { + path := filepath.Join(r.Prefix, name) + return r.Source.OpenResource(path) +} + +var _ PhysicalResources = &PathPhysicalResources{} + +// PathPhysicalResources implements PhysicalResources by adding a prefix to the requested source name before proxying it to its source. +type PathPhysicalResources struct { + Source PhysicalResources + Prefix string +} + +// NewPathPhysicalResources creates a new PhysicalResources by adding a prefix to the requested source name before proxying it to its source. If source is nil it will use the OS file system for its resources. +func NewPathPhysicalResources(source PhysicalResources, prefix string) *PathPhysicalResources { + if source == nil { + source = &OSResources{} + } + return &PathPhysicalResources{source, prefix} +} + +// Destroy destroys the source. +func (r *PathPhysicalResources) Destroy() error { return r.Source.Destroy() } + +// FetchResource fetches the resource with the prefixed name. +func (r *PathPhysicalResources) FetchResource(name string) (string, error) { + path := filepath.Join(r.Prefix, name) + return r.Source.FetchResource(path) +} + +// OpenResource opens the resource with the prefixed name. +func (r *PathPhysicalResources) OpenResource(name string) (io.ReadCloser, error) { + path := filepath.Join(r.Prefix, name) + return r.Source.OpenResource(path) +} diff --git a/ui/rendererfactory.go b/ui/rendererfactory.go index d89154a..6c3217a 100644 --- a/ui/rendererfactory.go +++ b/ui/rendererfactory.go @@ -18,12 +18,17 @@ func NewRenderer(title string, width, height int, opts NewRendererOptions) (Rend if rendererFactory == nil { return nil, errors.New("no renderer factory registered") } - return rendererFactory.New(title, width, height, opts) + renderer, err := rendererFactory.New(title, width, height, opts) + if err != nil { + return nil, err + } + renderer.SetResourceProvider(func() Resources { return DefaultResources() }) + return renderer, nil } // NewRendererDefault creates a new renderer with default options set based on the registered renderer factory. func NewRendererDefault(title string, width, height int) (Renderer, error) { - return rendererFactory.New(title, width, height, NewRendererOptions{ + return NewRenderer(title, width, height, NewRendererOptions{ Resizable: true, }) } diff --git a/ui/resources.go b/ui/resources.go index c8da887..0a9d474 100644 --- a/ui/resources.go +++ b/ui/resources.go @@ -2,12 +2,18 @@ package ui import "io" -// Resources is an abstraction on resources. +// Resources is an abstraction for opening 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 } + +// PhysicalResources is an abstraction for opening and fetching (to disk) resources. +type PhysicalResources interface { + Resources + + // 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) +}