2018-09-26 17:04:46 +00:00
|
|
|
package uix
|
2018-08-17 03:37:19 +00:00
|
|
|
|
|
|
|
import (
|
2018-10-08 20:06:42 +00:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2018-08-17 03:37:19 +00:00
|
|
|
"git.kirsle.net/apps/doodle/balance"
|
2018-09-26 17:04:46 +00:00
|
|
|
"git.kirsle.net/apps/doodle/doodads"
|
2018-08-17 03:37:19 +00:00
|
|
|
"git.kirsle.net/apps/doodle/events"
|
2018-09-26 17:04:46 +00:00
|
|
|
"git.kirsle.net/apps/doodle/level"
|
2018-10-19 20:31:58 +00:00
|
|
|
"git.kirsle.net/apps/doodle/pkg/userdir"
|
2018-08-17 03:37:19 +00:00
|
|
|
"git.kirsle.net/apps/doodle/render"
|
|
|
|
"git.kirsle.net/apps/doodle/ui"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Canvas is a custom ui.Widget that manages a single drawing.
|
|
|
|
type Canvas struct {
|
|
|
|
ui.Frame
|
2018-09-26 17:04:46 +00:00
|
|
|
Palette *level.Palette
|
2018-08-17 03:37:19 +00:00
|
|
|
|
2018-10-20 22:42:49 +00:00
|
|
|
// Editable and Scrollable go hand in hand and, if you initialize a
|
|
|
|
// NewCanvas() with editable=true, they are both enabled.
|
|
|
|
Editable bool // Clicking will edit pixels of this canvas.
|
|
|
|
Scrollable bool // Cursor keys will scroll the viewport of this canvas.
|
|
|
|
|
|
|
|
// MaskColor will force every pixel to render as this color regardless of
|
|
|
|
// the palette index of that pixel. Otherwise pixels behave the same and
|
|
|
|
// the palette does work as normal. Set to render.Invisible (zero value)
|
|
|
|
// to remove the mask.
|
|
|
|
MaskColor render.Color
|
2018-08-17 03:37:19 +00:00
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
// Underlying chunk data for the drawing.
|
|
|
|
chunks *level.Chunker
|
|
|
|
|
|
|
|
// Actors to superimpose on top of the drawing.
|
|
|
|
actor *level.Actor // if this canvas IS an actor
|
|
|
|
actors []*Actor
|
|
|
|
|
|
|
|
// Tracking pixels while editing. TODO: get rid of pixelHistory?
|
2018-09-26 17:04:46 +00:00
|
|
|
pixelHistory []*level.Pixel
|
|
|
|
lastPixel *level.Pixel
|
2018-08-17 03:37:19 +00:00
|
|
|
|
|
|
|
// We inherit the ui.Widget which manages the width and height.
|
|
|
|
Scroll render.Point // Scroll offset for which parts of canvas are visible.
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
// Actor is an instance of an actor with a Canvas attached.
|
|
|
|
type Actor struct {
|
|
|
|
Actor *level.Actor
|
|
|
|
Canvas *Canvas
|
|
|
|
}
|
|
|
|
|
2018-08-17 03:37:19 +00:00
|
|
|
// NewCanvas initializes a Canvas widget.
|
2018-09-26 17:04:46 +00:00
|
|
|
//
|
|
|
|
// If editable is true, Scrollable is also set to true, which means the arrow
|
|
|
|
// keys will scroll the canvas viewport which is desirable in Edit Mode.
|
2018-09-23 22:20:45 +00:00
|
|
|
func NewCanvas(size int, editable bool) *Canvas {
|
2018-08-17 03:37:19 +00:00
|
|
|
w := &Canvas{
|
2018-09-26 17:04:46 +00:00
|
|
|
Editable: editable,
|
|
|
|
Scrollable: editable,
|
|
|
|
Palette: level.NewPalette(),
|
|
|
|
chunks: level.NewChunker(size),
|
2018-10-19 20:31:58 +00:00
|
|
|
actors: make([]*Actor, 0),
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
w.setup()
|
2018-10-08 17:38:49 +00:00
|
|
|
w.IDFunc(func() string {
|
2018-10-08 20:06:42 +00:00
|
|
|
var attrs []string
|
|
|
|
|
|
|
|
if w.Editable {
|
|
|
|
attrs = append(attrs, "editable")
|
|
|
|
} else {
|
|
|
|
attrs = append(attrs, "read-only")
|
|
|
|
}
|
|
|
|
|
|
|
|
if w.Scrollable {
|
|
|
|
attrs = append(attrs, "scrollable")
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("Canvas<%d; %s>", size, strings.Join(attrs, "; "))
|
2018-10-08 17:38:49 +00:00
|
|
|
})
|
2018-08-17 03:37:19 +00:00
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load initializes the Canvas using an existing Palette and Grid.
|
2018-09-26 17:04:46 +00:00
|
|
|
func (w *Canvas) Load(p *level.Palette, g *level.Chunker) {
|
2018-08-17 03:37:19 +00:00
|
|
|
w.Palette = p
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks = g
|
2018-08-17 03:37:19 +00:00
|
|
|
|
|
|
|
if len(w.Palette.Swatches) > 0 {
|
|
|
|
w.SetSwatch(w.Palette.Swatches[0])
|
|
|
|
}
|
2018-09-25 16:40:34 +00:00
|
|
|
}
|
2018-08-17 03:37:19 +00:00
|
|
|
|
2018-09-25 16:40:34 +00:00
|
|
|
// LoadLevel initializes a Canvas from a Level object.
|
2018-09-26 17:04:46 +00:00
|
|
|
func (w *Canvas) LoadLevel(level *level.Level) {
|
2018-09-25 16:40:34 +00:00
|
|
|
w.Load(level.Palette, level.Chunker)
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// LoadDoodad initializes a Canvas from a Doodad object.
|
|
|
|
func (w *Canvas) LoadDoodad(d *doodads.Doodad) {
|
|
|
|
// TODO more safe
|
|
|
|
w.Load(d.Palette, d.Layers[0].Chunker)
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
// InstallActors adds external Actors to the canvas to be superimposed on top
|
|
|
|
// of the drawing.
|
|
|
|
func (w *Canvas) InstallActors(actors level.ActorMap) error {
|
|
|
|
w.actors = make([]*Actor, 0)
|
|
|
|
for id, actor := range actors {
|
|
|
|
log.Info("InstallActors: %s", id)
|
|
|
|
|
|
|
|
doodad, err := doodads.LoadJSON(userdir.DoodadPath(actor.Filename))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("InstallActors: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
size := int32(doodad.Layers[0].Chunker.Size)
|
|
|
|
can := NewCanvas(int(size), false)
|
|
|
|
can.Name = id
|
|
|
|
can.actor = actor
|
2018-10-20 22:42:49 +00:00
|
|
|
// TODO: if the Background is render.Invisible it gets defaulted to
|
|
|
|
// White somewhere and the Doodad masks the level drawing behind it.
|
|
|
|
can.SetBackground(render.RGBA(0, 0, 1, 0))
|
2018-10-19 20:31:58 +00:00
|
|
|
can.LoadDoodad(doodad)
|
|
|
|
can.Resize(render.NewRect(size, size))
|
|
|
|
w.actors = append(w.actors, &Actor{
|
|
|
|
Actor: actor,
|
|
|
|
Canvas: can,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-17 03:37:19 +00:00
|
|
|
// SetSwatch changes the currently selected swatch for editing.
|
2018-09-26 17:04:46 +00:00
|
|
|
func (w *Canvas) SetSwatch(s *level.Swatch) {
|
2018-08-17 03:37:19 +00:00
|
|
|
w.Palette.ActiveSwatch = s
|
|
|
|
}
|
|
|
|
|
|
|
|
// setup common configs between both initializers of the canvas.
|
|
|
|
func (w *Canvas) setup() {
|
2018-10-19 20:31:58 +00:00
|
|
|
// XXX: Debug code.
|
|
|
|
if balance.DebugCanvasBorder != render.Invisible {
|
|
|
|
w.Configure(ui.Config{
|
|
|
|
BorderColor: balance.DebugCanvasBorder,
|
|
|
|
BorderSize: 2,
|
|
|
|
BorderStyle: ui.BorderSolid,
|
|
|
|
})
|
|
|
|
}
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Loop is called on the scene's event loop to handle mouse interaction with
|
|
|
|
// the canvas, i.e. to edit it.
|
|
|
|
func (w *Canvas) Loop(ev *events.State) error {
|
2018-10-08 17:38:49 +00:00
|
|
|
// Get the absolute position of the canvas on screen to accurately match
|
|
|
|
// it up to mouse clicks.
|
|
|
|
var P = ui.AbsolutePosition(w)
|
2018-08-17 03:37:19 +00:00
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
if w.Scrollable {
|
|
|
|
// Arrow keys to scroll the view.
|
|
|
|
scrollBy := render.Point{}
|
|
|
|
if ev.Right.Now {
|
|
|
|
scrollBy.X -= balance.CanvasScrollSpeed
|
2018-10-18 06:01:21 +00:00
|
|
|
} else if ev.Left.Now {
|
|
|
|
scrollBy.X += balance.CanvasScrollSpeed
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
|
|
|
if ev.Down.Now {
|
|
|
|
scrollBy.Y -= balance.CanvasScrollSpeed
|
2018-10-18 06:01:21 +00:00
|
|
|
} else if ev.Up.Now {
|
|
|
|
scrollBy.Y += balance.CanvasScrollSpeed
|
2018-09-26 17:04:46 +00:00
|
|
|
}
|
|
|
|
if !scrollBy.IsZero() {
|
|
|
|
w.ScrollBy(scrollBy)
|
|
|
|
}
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Only care if the cursor is over our space.
|
|
|
|
cursor := render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
|
2018-10-08 17:38:49 +00:00
|
|
|
if !cursor.Inside(ui.AbsoluteRect(w)) {
|
2018-08-17 03:37:19 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no swatch is active, do nothing with mouse clicks.
|
|
|
|
if w.Palette.ActiveSwatch == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clicking? Log all the pixels while doing so.
|
|
|
|
if ev.Button1.Now {
|
|
|
|
lastPixel := w.lastPixel
|
2018-09-23 22:20:45 +00:00
|
|
|
cursor := render.Point{
|
2018-10-18 06:01:21 +00:00
|
|
|
X: ev.CursorX.Now - P.X - w.Scroll.X,
|
|
|
|
Y: ev.CursorY.Now - P.Y - w.Scroll.Y,
|
2018-09-23 22:20:45 +00:00
|
|
|
}
|
2018-09-26 17:04:46 +00:00
|
|
|
pixel := &level.Pixel{
|
2018-09-25 16:40:34 +00:00
|
|
|
X: cursor.X,
|
|
|
|
Y: cursor.Y,
|
|
|
|
Swatch: w.Palette.ActiveSwatch,
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Append unique new pixels.
|
|
|
|
if len(w.pixelHistory) == 0 || w.pixelHistory[len(w.pixelHistory)-1] != pixel {
|
|
|
|
if lastPixel != nil {
|
|
|
|
// Draw the pixels in between.
|
|
|
|
if lastPixel != pixel {
|
|
|
|
for point := range render.IterLine(lastPixel.X, lastPixel.Y, pixel.X, pixel.Y) {
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks.Set(point, lastPixel.Swatch)
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
w.lastPixel = pixel
|
|
|
|
w.pixelHistory = append(w.pixelHistory, pixel)
|
|
|
|
|
|
|
|
// Save in the pixel canvas map.
|
2018-09-23 22:20:45 +00:00
|
|
|
w.chunks.Set(cursor, pixel.Swatch)
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
w.lastPixel = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Viewport returns a rect containing the viewable drawing coordinates in this
|
|
|
|
// canvas. The X,Y values are the scroll offset (top left) and the W,H values
|
|
|
|
// are the scroll offset plus the width/height of the Canvas widget.
|
2018-10-19 20:31:58 +00:00
|
|
|
//
|
|
|
|
// The Viewport rect are the Absolute World Coordinates of the drawing that are
|
|
|
|
// visible inside the Canvas. The X,Y is the top left World Coordinate and the
|
|
|
|
// W,H are the bottom right World Coordinate, making this rect an absolute
|
|
|
|
// slice of the world. For a normal rect with a relative width and height,
|
|
|
|
// use ViewportRelative().
|
|
|
|
//
|
|
|
|
// The rect X,Y are the negative Scroll Value.
|
|
|
|
// The rect W,H are the Canvas widget size minus the Scroll Value.
|
2018-08-17 03:37:19 +00:00
|
|
|
func (w *Canvas) Viewport() render.Rect {
|
|
|
|
var S = w.Size()
|
|
|
|
return render.Rect{
|
2018-10-18 06:01:21 +00:00
|
|
|
X: -w.Scroll.X,
|
|
|
|
Y: -w.Scroll.Y,
|
|
|
|
W: S.W - w.Scroll.X,
|
|
|
|
H: S.H - w.Scroll.Y,
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
// ViewportRelative returns a relative viewport where the Width and Height
|
|
|
|
// values are zero-relative: so you can use it with point.Inside(viewport)
|
|
|
|
// to see if a World Index point should be visible on screen.
|
|
|
|
//
|
|
|
|
// The rect X,Y are the negative Scroll Value
|
|
|
|
// The rect W,H are the Canvas widget size.
|
|
|
|
func (w *Canvas) ViewportRelative() render.Rect {
|
|
|
|
var S = w.Size()
|
|
|
|
return render.Rect{
|
|
|
|
X: -w.Scroll.X,
|
|
|
|
Y: -w.Scroll.Y,
|
|
|
|
W: S.W,
|
|
|
|
H: S.H,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-20 22:42:49 +00:00
|
|
|
// WorldIndexAt returns the World Index that corresponds to a Screen Pixel
|
|
|
|
// on the screen. If the screen pixel is the mouse coordinate (relative to
|
|
|
|
// the application window) this will return the World Index of the pixel below
|
|
|
|
// the mouse cursor.
|
|
|
|
func (w *Canvas) WorldIndexAt(screenPixel render.Point) render.Point {
|
|
|
|
var P = ui.AbsolutePosition(w)
|
|
|
|
return render.Point{
|
|
|
|
X: screenPixel.X - P.X - w.Scroll.X,
|
|
|
|
Y: screenPixel.Y - P.Y - w.Scroll.Y,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-23 22:20:45 +00:00
|
|
|
// Chunker returns the underlying Chunker object.
|
2018-09-26 17:04:46 +00:00
|
|
|
func (w *Canvas) Chunker() *level.Chunker {
|
2018-09-23 22:20:45 +00:00
|
|
|
return w.chunks
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
|
|
|
|
2018-09-26 17:04:46 +00:00
|
|
|
// ScrollTo sets the viewport scroll position.
|
|
|
|
func (w *Canvas) ScrollTo(to render.Point) {
|
|
|
|
w.Scroll.X = to.X
|
|
|
|
w.Scroll.Y = to.Y
|
|
|
|
}
|
|
|
|
|
2018-08-17 03:37:19 +00:00
|
|
|
// ScrollBy adjusts the viewport scroll position.
|
|
|
|
func (w *Canvas) ScrollBy(by render.Point) {
|
|
|
|
w.Scroll.Add(by)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compute the canvas.
|
|
|
|
func (w *Canvas) Compute(e render.Engine) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Present the canvas.
|
|
|
|
func (w *Canvas) Present(e render.Engine, p render.Point) {
|
|
|
|
var (
|
|
|
|
S = w.Size()
|
|
|
|
Viewport = w.Viewport()
|
|
|
|
)
|
2018-10-08 17:38:49 +00:00
|
|
|
// w.MoveTo(p) // TODO: when uncommented the canvas will creep down the Workspace frame in EditorMode
|
2018-08-17 03:37:19 +00:00
|
|
|
w.DrawBox(e, p)
|
|
|
|
e.DrawBox(w.Background(), render.Rect{
|
|
|
|
X: p.X + w.BoxThickness(1),
|
|
|
|
Y: p.Y + w.BoxThickness(1),
|
|
|
|
W: S.W - w.BoxThickness(2),
|
|
|
|
H: S.H - w.BoxThickness(2),
|
|
|
|
})
|
|
|
|
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
// Get the chunks in the viewport and cache their textures.
|
|
|
|
for coord := range w.chunks.IterViewportChunks(Viewport) {
|
|
|
|
if chunk, ok := w.chunks.GetChunk(coord); ok {
|
2018-10-20 22:42:49 +00:00
|
|
|
var tex render.Texturer
|
|
|
|
if w.MaskColor != render.Invisible {
|
|
|
|
tex = chunk.TextureMasked(e, w.MaskColor)
|
|
|
|
} else {
|
|
|
|
tex = chunk.Texture(e)
|
|
|
|
}
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
src := render.Rect{
|
|
|
|
W: tex.Size().W,
|
|
|
|
H: tex.Size().H,
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the source bitmap is already bigger than the Canvas widget
|
|
|
|
// into which it will render, cap the source width and height.
|
|
|
|
// This is especially useful for Doodad buttons because the drawing
|
|
|
|
// is bigger than the button.
|
|
|
|
if src.W > S.W {
|
|
|
|
src.W = S.W
|
|
|
|
}
|
|
|
|
if src.H > S.H {
|
|
|
|
src.H = S.H
|
|
|
|
}
|
|
|
|
|
|
|
|
dst := render.Rect{
|
|
|
|
X: p.X + w.Scroll.X + w.BoxThickness(1) + (coord.X * int32(chunk.Size)),
|
|
|
|
Y: p.Y + w.Scroll.Y + w.BoxThickness(1) + (coord.Y * int32(chunk.Size)),
|
|
|
|
|
|
|
|
// src.W and src.H will be AT MOST the full width and height of
|
|
|
|
// a Canvas widget. Subtract the scroll offset to keep it bounded
|
|
|
|
// visually on its right and bottom sides.
|
2018-10-19 20:31:58 +00:00
|
|
|
W: src.W,
|
|
|
|
H: src.H,
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If the destination width will cause it to overflow the widget
|
|
|
|
// box, trim off the right edge of the destination rect.
|
|
|
|
//
|
|
|
|
// Keep in mind we're dealing with chunks here, and a chunk is
|
|
|
|
// a small part of the image. Example:
|
|
|
|
// - Canvas is 800x600 (S.W=800 S.H=600)
|
|
|
|
// - Chunk wants to render at 790,0 width 100,100 or whatever
|
|
|
|
// dst={790, 0, 100, 100}
|
|
|
|
// - Chunk box would exceed 800px width (X=790 + W=100 == 890)
|
|
|
|
// - Find the delta how much it exceeds as negative (800 - 890 == -90)
|
|
|
|
// - Lower the Source and Dest rects by that delta size so they
|
|
|
|
// stay proportional and don't scale or anything dumb.
|
|
|
|
if dst.X+src.W > p.X+S.W {
|
|
|
|
// NOTE: delta is a negative number,
|
|
|
|
// so it will subtract from the width.
|
2018-10-19 20:31:58 +00:00
|
|
|
delta := (p.X + S.W - w.BoxThickness(1)) - (dst.W + dst.X)
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
src.W += delta
|
|
|
|
dst.W += delta
|
|
|
|
}
|
|
|
|
if dst.Y+src.H > p.Y+S.H {
|
|
|
|
// NOTE: delta is a negative number
|
2018-10-19 20:31:58 +00:00
|
|
|
delta := (p.Y + S.H - w.BoxThickness(1)) - (dst.H + dst.Y)
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
src.H += delta
|
|
|
|
dst.H += delta
|
|
|
|
}
|
|
|
|
|
|
|
|
// The same for the top left edge, so the drawings don't overlap
|
|
|
|
// menu bars or left side toolbars.
|
|
|
|
// - Canvas was placed 80px from the left of the screen.
|
|
|
|
// Canvas.MoveTo(80, 0)
|
|
|
|
// - A texture wants to draw at 60, 0 which would cause it to
|
|
|
|
// overlap 20 pixels into the left toolbar. It needs to be cropped.
|
|
|
|
// - The delta is: p.X=80 - dst.X=60 == 20
|
|
|
|
// - Set destination X to p.X to constrain it there: 20
|
|
|
|
// - Subtract the delta from destination W so we don't scale it.
|
|
|
|
// - Add 20 to X of the source: the left edge of source is not visible
|
|
|
|
if dst.X < p.X {
|
|
|
|
// NOTE: delta is a positive number,
|
|
|
|
// so it will add to the destination coordinates.
|
|
|
|
delta := p.X - dst.X
|
2018-10-19 20:31:58 +00:00
|
|
|
dst.X = p.X + w.BoxThickness(1)
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
dst.W -= delta
|
|
|
|
src.X += delta
|
|
|
|
}
|
|
|
|
if dst.Y < p.Y {
|
|
|
|
delta := p.Y - dst.Y
|
2018-10-19 20:31:58 +00:00
|
|
|
dst.Y = p.Y + w.BoxThickness(1)
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
dst.H -= delta
|
|
|
|
src.Y += delta
|
|
|
|
}
|
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
// Trim the destination width so it doesn't overlap the Canvas border.
|
|
|
|
if dst.W >= S.W-w.BoxThickness(1) {
|
|
|
|
dst.W = S.W - w.BoxThickness(1)
|
|
|
|
}
|
|
|
|
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
e.Copy(tex, src, dst)
|
|
|
|
}
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|
WIP Texture Caching
NOTICE: Chunk size set to 100 for visual testing!
NOTICE: guitest references a bmp file that isn't checked in!
BUGS REMAINING:
- When scrolling the level in Edit Mode, some of the chunks will pop
out of existence randomly.
- When clicking-dragging to draw in Edit Mode, if the scroll position
is not at 0,0 then the pixels drawn will be offset from the cursor.
- These are to do with the Scroll position and chunk coordinate calc
functions probably.
Implements a texture caching interface to stop redrawing everything
pixel by pixel on every frame.
The texture caching workflow is briefly:
- The uix.Canvas widget's Present() function iterates over the list of
Chunk Coordinates that are visible inside of the current viewport
(i.e. viewable on screen)
- For each Chunk:
- Make it render and/or return its cached Texture object.
- Work out how much of the Chunk will be visible and how to crop the
boxes for the Copy()
- Copy the cached Texture instead of drawing all the pixels every
time like we were doing before.
- The Chunk.Texture() function that returns said Texture:
- It calls Chunk.ToBitmap() to save a bitmap on disk.
- It calls Engine.NewBitmap() to get a Texture it can hang onto.
- It hangs onto the Texture and returns it on future calls.
- Any call to Set() or Delete() a pixel will invalidate the cache
(mark the Chunk "dirty") and Texture() will rebuild next call.
The interface `render.Texturer` provides a way for rendering backends
(SDL2, OpenGL) to transport a "texture" of their own kind without
exposing the type details to the user.
The interface `render.Engine` adds two new methods:
* NewBitmap(filename string) (Texturer, error)
* Copy(t Texturer, src, dst Rect)
NewBitmap should open a bitmap image on disk and return it wrapped in a
Texturer (really it's an SDL2 Texture). This is for caching purposes.
Next the Copy() function blits the texture onto the screen renderer
using the source and destination rectangles.
The uix.Canvas widget orchestrates the caching for the drawing it's
responsible for. It queries which chunks are viewable in the Canvas
viewport (scroll and bounding boxes), has each chunk render out their
entire bitmap image to then cache them as SDL textures and then only
_those_ need to be copied out to the renderer each frame.
The frame rate now sits at a decent 60 FPS even when the drawing gets
messy and full of lines. Each unique version of each chunk needs to
render only one time and then it's a fast copy operation for future
ticks.
Other changes:
- Chunker now assigns each Chunk what their coordinate and size are, so
that the chunk can self reference that information. This info is
considered read-only but that isn't really enforced.
- Add Chunker.IterViewportChunks() that returns a channel of Chunk
Coordinates that are visible in your viewport, rather than iterating
over all of the pixels in all of those chunks.
- Add Chunk.ToBitmap(filename) that causes a Chunk to render its pixels
to a bitmap image on disk. SDL2 can natively speak Bitmaps for texture
caching. Currently these go to files in /tmp but will soon go into your
$XDG_CACHE_FOLDER instead.
- Add Chunk.Texture() that causes a Chunk to render and then return a
cached bitmap texture of the pixels it's responsible for. The texture
is cached until the Chunk is next modified with Set() or Delete().
- UI: add an Image widget that currently just shows a bitmap image. It
was the first test for caching bitmap images for efficiency. Can show
any *.bmp file on disk!
- Editor UI: make the StatusBar boxes dynamically build from an array
of string pointers to make it SUPER EASY to add/remove labels.
2018-10-18 03:52:14 +00:00
|
|
|
|
2018-10-19 20:31:58 +00:00
|
|
|
w.drawActors(e, p)
|
|
|
|
|
|
|
|
// XXX: Debug, show label in canvas corner.
|
|
|
|
if balance.DebugCanvasLabel {
|
|
|
|
rows := []string{
|
|
|
|
w.Name,
|
|
|
|
|
|
|
|
// XXX: debug options, uncomment for more details
|
|
|
|
|
|
|
|
// Size of the canvas
|
|
|
|
// fmt.Sprintf("S=%d,%d", S.W, S.H),
|
|
|
|
|
|
|
|
// Viewport of the canvas
|
|
|
|
// fmt.Sprintf("V=%d,%d:%d,%d",
|
|
|
|
// Viewport.X, Viewport.Y,
|
|
|
|
// Viewport.W, Viewport.H,
|
|
|
|
// ),
|
|
|
|
}
|
|
|
|
if w.actor != nil {
|
|
|
|
rows = append(rows,
|
|
|
|
fmt.Sprintf("WP=%s", w.actor.Point),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
label := ui.NewLabel(ui.Label{
|
|
|
|
Text: strings.Join(rows, "\n"),
|
|
|
|
Font: render.Text{
|
|
|
|
FontFilename: balance.ShellFontFilename,
|
|
|
|
Size: balance.ShellFontSizeSmall,
|
|
|
|
Color: render.White,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
label.SetBackground(render.RGBA(0, 0, 50, 150))
|
|
|
|
label.Compute(e)
|
|
|
|
label.Present(e, render.Point{
|
|
|
|
X: p.X + S.W - label.Size().W - w.BoxThickness(1),
|
|
|
|
Y: p.Y + w.BoxThickness(1),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// drawActors superimposes the actors on top of the drawing.
|
|
|
|
func (w *Canvas) drawActors(e render.Engine, p render.Point) {
|
|
|
|
var (
|
|
|
|
Viewport = w.ViewportRelative()
|
|
|
|
S = w.Size()
|
|
|
|
)
|
|
|
|
|
|
|
|
// See if each Actor is in range of the Viewport.
|
|
|
|
for _, a := range w.actors {
|
|
|
|
var (
|
|
|
|
actor = a.Actor // Static Actor instance from Level file, DO NOT CHANGE
|
|
|
|
can = a.Canvas // Canvas widget that draws the actor
|
|
|
|
actorPoint = actor.Point // XXX TODO: DO NOT CHANGE
|
|
|
|
actorSize = can.Size()
|
|
|
|
)
|
|
|
|
|
|
|
|
// Create a box of World Coordinates that this actor occupies. The
|
|
|
|
// Actor X,Y from level data is already a World Coordinate;
|
|
|
|
// accomodate for the size of the Actor.
|
|
|
|
actorBox := render.Rect{
|
|
|
|
X: actorPoint.X,
|
|
|
|
Y: actorPoint.Y,
|
|
|
|
W: actorSize.W,
|
|
|
|
H: actorSize.H,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is any part of the actor visible?
|
|
|
|
if !Viewport.Intersects(actorBox) {
|
|
|
|
continue // not visible on screen
|
|
|
|
}
|
|
|
|
|
|
|
|
drawAt := render.Point{
|
|
|
|
X: p.X + w.Scroll.X + actorPoint.X + w.BoxThickness(1),
|
|
|
|
Y: p.Y + w.Scroll.Y + actorPoint.Y + w.BoxThickness(1),
|
|
|
|
}
|
|
|
|
resizeTo := actorSize
|
|
|
|
|
|
|
|
// XXX TODO: when an Actor hits the left or top edge and shrinks,
|
|
|
|
// scrolling to offset that shrink is currently hard to solve.
|
|
|
|
scrollTo := render.Origin
|
|
|
|
|
|
|
|
// Handle cropping and scaling if this Actor's canvas can't be
|
|
|
|
// completely visible within the parent.
|
|
|
|
if drawAt.X+resizeTo.W > p.X+S.W {
|
|
|
|
// Hitting the right edge, shrunk the width now.
|
|
|
|
delta := (drawAt.X + resizeTo.W) - (p.X + S.W)
|
|
|
|
resizeTo.W -= delta
|
|
|
|
} else if drawAt.X < p.X {
|
|
|
|
// Hitting the left edge. Cap the X coord and shrink the width.
|
|
|
|
delta := p.X - drawAt.X // positive number
|
|
|
|
drawAt.X = p.X
|
|
|
|
// scrollTo.X -= delta // TODO
|
|
|
|
resizeTo.W -= delta
|
|
|
|
}
|
|
|
|
|
|
|
|
if drawAt.Y+resizeTo.H > p.Y+S.H {
|
|
|
|
// Hitting the bottom edge, shrink the height.
|
|
|
|
delta := (drawAt.Y + resizeTo.H) - (p.Y + S.H)
|
|
|
|
resizeTo.H -= delta
|
|
|
|
} else if drawAt.Y < p.Y {
|
|
|
|
// Hitting the top edge. Cap the Y coord and shrink the height.
|
|
|
|
delta := p.Y - drawAt.Y
|
|
|
|
drawAt.Y = p.Y
|
|
|
|
// scrollTo.Y -= delta // TODO
|
|
|
|
resizeTo.H -= delta
|
|
|
|
}
|
|
|
|
|
|
|
|
if resizeTo != actorSize {
|
|
|
|
can.Resize(resizeTo)
|
|
|
|
can.ScrollTo(scrollTo)
|
|
|
|
}
|
|
|
|
can.Present(e, drawAt)
|
|
|
|
|
|
|
|
// Clean up the canvas size and offset.
|
|
|
|
can.Resize(actorSize) // restore original size in case cropped
|
|
|
|
can.ScrollTo(render.Origin)
|
|
|
|
}
|
2018-08-17 03:37:19 +00:00
|
|
|
}
|