273 lines
8.4 KiB
Go
273 lines
8.4 KiB
Go
package uix
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
|
|
"git.kirsle.net/SketchyMaze/doodle/pkg/log"
|
|
"git.kirsle.net/go/render"
|
|
"git.kirsle.net/go/ui"
|
|
)
|
|
|
|
// Present the canvas.
|
|
func (w *Canvas) Present(e render.Engine, p render.Point) {
|
|
var (
|
|
S = w.Size()
|
|
Viewport = w.Viewport()
|
|
// Bezel = render.NewRect(
|
|
// p.X+w.Scroll.X+w.BoxThickness(1),
|
|
// p.Y+w.Scroll.Y+w.BoxThickness(1),
|
|
// )
|
|
// zoomMultiplier = int(w.GetZoomMultiplier())
|
|
)
|
|
// w.MoveTo(p) // TODO: when uncommented the canvas will creep down the Workspace frame in EditorMode
|
|
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),
|
|
})
|
|
|
|
// If we are an Actor canvas as part of a Level, get the absolute position of
|
|
// the parent (Level) canvas so we can compare where the Actor is drawn on-screen
|
|
// and detect if we are at the Top or Left edges of the parent, to crop and adjust
|
|
// the texture accordingly.
|
|
var ParentPosition render.Point
|
|
if w.parent != nil {
|
|
ParentPosition = ui.AbsolutePosition(w.parent)
|
|
}
|
|
|
|
// Draw the wallpaper.
|
|
if w.wallpaper.Valid() {
|
|
err := w.PresentWallpaper(e, p)
|
|
if err != nil {
|
|
log.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
// Scale the viewport to account for zoom level.
|
|
if w.Zoom != 0 {
|
|
// Zoomed out (level go tiny)
|
|
// TODO: seems unstable as shit on Zoom In??
|
|
Viewport.W = w.ZoomDivide(Viewport.W)
|
|
Viewport.H = w.ZoomDivide(Viewport.H)
|
|
if w.Zoom != 0 {
|
|
Viewport.X = w.ZoomDivide(Viewport.X)
|
|
Viewport.Y = w.ZoomDivide(Viewport.Y)
|
|
}
|
|
}
|
|
|
|
// Disappearing chunks issue:
|
|
// When at the top-left corner of a bounded level,
|
|
// And Zoom=2 (200% zoom), Level Chunk Size=128,
|
|
// When you scroll down exactly -128 pixels, the whole row
|
|
// of chunks along the top edge of the viewport unload.
|
|
// At -256, the next row of chunks unloads.
|
|
// At -383, another row - it's creeping down the page with
|
|
// the top 1/3 of the level editor showing blank wallpaper
|
|
// At -768, about 3/4 the level editor is blank
|
|
//
|
|
// It must think the upper 128px of chunks had left the
|
|
// viewport when in fact the bottom half of them was still
|
|
// in view, not respecting the 2X zoom level.
|
|
//
|
|
// Viewport is like: Rect<128,0,1058,721>
|
|
// Level chunks would be:
|
|
// (0, 0) = (0,0, 127,127) chunk A
|
|
// (1, 0) = (128,128, 255,255) chunk B
|
|
// At 2x zoom, Chunk A is still half on screen at -128 scroll
|
|
// if w.Zoom > 0 {
|
|
// Viewport.X = w.ZoomDivide(w.chunks.Size)
|
|
// Viewport.Y = w.ZoomDivide(w.chunks.Size)
|
|
// }
|
|
// Seems resolved now?
|
|
|
|
// 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 {
|
|
var tex render.Texturer
|
|
if w.MaskColor != render.Invisible {
|
|
tex = chunk.TextureMasked(e, w.MaskColor)
|
|
} else {
|
|
tex = chunk.Texture(e)
|
|
}
|
|
|
|
// Zoom in the texture.
|
|
var (
|
|
texSize = tex.Size()
|
|
texSizeOrig = texSize
|
|
)
|
|
|
|
if w.Zoom != 0 {
|
|
texSize.W = w.ZoomMultiply(texSize.W)
|
|
texSize.H = w.ZoomMultiply(texSize.H)
|
|
}
|
|
src := render.Rect{
|
|
W: texSize.W,
|
|
H: texSize.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) + w.ZoomMultiply(coord.X*chunk.Size),
|
|
Y: p.Y + w.Scroll.Y + w.BoxThickness(1) + w.ZoomMultiply(coord.Y*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.
|
|
W: src.W,
|
|
H: src.H,
|
|
}
|
|
|
|
// TODO: all this shit is in TrimBox(), make it DRY
|
|
|
|
// 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+w.BoxThickness(1) {
|
|
// NOTE: delta is a negative number,
|
|
// so it will subtract from the width.
|
|
delta := (p.X + S.W - w.BoxThickness(1)) - (dst.W + dst.X)
|
|
src.W += delta
|
|
dst.W += delta
|
|
}
|
|
if dst.Y+src.H > p.Y+S.H+w.BoxThickness(1) {
|
|
// NOTE: delta is a negative number
|
|
delta := (p.Y + S.H - w.BoxThickness(1)) - (dst.H + dst.Y)
|
|
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
|
|
//
|
|
// Note: the +w.BoxThickness works around a bug if the Actor Canvas has
|
|
// a border on it (e.g. in the Actor/Link Tool mouse-over or debug setting)
|
|
if dst.X == ParentPosition.X+w.BoxThickness(1) {
|
|
// NOTE: delta is a positive number,
|
|
// so it will add to the destination coordinates.
|
|
delta := texSizeOrig.W - src.W
|
|
dst.X = p.X + w.BoxThickness(1)
|
|
src.X += delta
|
|
}
|
|
if dst.Y == ParentPosition.Y+w.BoxThickness(1) {
|
|
delta := texSizeOrig.H - src.H
|
|
dst.Y = p.Y + w.BoxThickness(1)
|
|
src.Y += delta
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// When zooming OUT, make sure the source rect is at least the
|
|
// full size of the chunk texture; otherwise the ZoomMultiplies
|
|
// above do correctly scale e.g. 128x128 to 64x64, but it only
|
|
// samples the top-left 64x64 then and not the full texture so
|
|
// it more crops it than scales it, but does fit it neatly with
|
|
// its neighbors.
|
|
if w.Zoom < 0 {
|
|
src.W = texSizeOrig.W
|
|
src.H = texSizeOrig.H
|
|
}
|
|
|
|
e.Copy(tex, src, dst)
|
|
}
|
|
}
|
|
|
|
w.drawActors(e, p)
|
|
w.presentStrokes(e)
|
|
w.presentCursor(e)
|
|
|
|
// Custom label in the canvas corner? (e.g. for Inventory item counts)
|
|
if w.CornerLabel != "" {
|
|
label := ui.NewLabel(ui.Label{
|
|
Text: w.CornerLabel,
|
|
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 + S.H - label.Size().H - w.BoxThickness(1),
|
|
})
|
|
}
|
|
|
|
// 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,
|
|
// ),
|
|
}
|
|
|
|
// Draw the actor's position details.
|
|
// LP = Level Position, where the Actor starts at in the level data
|
|
// WP = World Position, the Actor's current position in the level
|
|
if w.actor != nil {
|
|
rows = append(rows,
|
|
fmt.Sprintf("LP=%s", w.actor.Actor.Point),
|
|
fmt.Sprintf("WP=%s", w.actor.Position()),
|
|
)
|
|
}
|
|
|
|
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),
|
|
})
|
|
}
|
|
}
|