2019-04-10 02:17:56 +00:00
|
|
|
package uix
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/balance"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/level"
|
2019-12-28 03:16:34 +00:00
|
|
|
"git.kirsle.net/go/render"
|
|
|
|
"git.kirsle.net/go/render/event"
|
2019-04-10 02:17:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
|
|
Loop() subroutine to scroll the canvas using arrow keys (for edit mode).
|
|
|
|
|
|
|
|
If w.Scrollable is false this function won't do anything.
|
|
|
|
|
|
|
|
Cursor keys will scroll the drawing by balance.CanvasScrollSpeed per tick.
|
|
|
|
If the level pageType is constrained, the scrollable viewport will be
|
|
|
|
constrained to fit the bounds of the level.
|
|
|
|
|
|
|
|
The debug boolean `NoLimitScroll=true` will override the bounded level scroll
|
|
|
|
restriction and allow scrolling into out-of-bounds areas of the level.
|
|
|
|
*/
|
2019-12-22 22:11:01 +00:00
|
|
|
func (w *Canvas) loopEditorScroll(ev *event.State) error {
|
2019-04-10 02:17:56 +00:00
|
|
|
if !w.Scrollable {
|
|
|
|
return errors.New("canvas not scrollable")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Arrow keys to scroll the view.
|
|
|
|
scrollBy := render.Point{}
|
2019-12-22 22:11:01 +00:00
|
|
|
if ev.Right {
|
2019-04-10 02:17:56 +00:00
|
|
|
scrollBy.X -= balance.CanvasScrollSpeed
|
2019-12-22 22:11:01 +00:00
|
|
|
} else if ev.Left {
|
2019-04-10 02:17:56 +00:00
|
|
|
scrollBy.X += balance.CanvasScrollSpeed
|
|
|
|
}
|
2019-12-22 22:11:01 +00:00
|
|
|
if ev.Down {
|
2019-04-10 02:17:56 +00:00
|
|
|
scrollBy.Y -= balance.CanvasScrollSpeed
|
2019-12-22 22:11:01 +00:00
|
|
|
} else if ev.Up {
|
2019-04-10 02:17:56 +00:00
|
|
|
scrollBy.Y += balance.CanvasScrollSpeed
|
|
|
|
}
|
|
|
|
if !scrollBy.IsZero() {
|
|
|
|
w.ScrollBy(scrollBy)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Loop() subroutine to constrain the scrolled view to within a bounded level.
|
|
|
|
*/
|
|
|
|
func (w *Canvas) loopConstrainScroll() error {
|
|
|
|
if w.NoLimitScroll {
|
|
|
|
return errors.New("NoLimitScroll enabled")
|
|
|
|
}
|
|
|
|
|
|
|
|
var capped bool
|
|
|
|
|
|
|
|
// Constrain the top and left edges.
|
|
|
|
if w.wallpaper.pageType > level.Unbounded {
|
|
|
|
if w.Scroll.X > 0 {
|
|
|
|
w.Scroll.X = 0
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
if w.Scroll.Y > 0 {
|
|
|
|
w.Scroll.Y = 0
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Constrain the bottom and right for limited world sizes.
|
2019-06-25 21:57:11 +00:00
|
|
|
if w.wallpaper.pageType >= level.Bounded &&
|
|
|
|
w.wallpaper.maxWidth+w.wallpaper.maxHeight > 0 {
|
2019-04-10 02:17:56 +00:00
|
|
|
var (
|
|
|
|
// TODO: downcast from int64!
|
2019-12-28 03:16:34 +00:00
|
|
|
mw = int(w.wallpaper.maxWidth)
|
|
|
|
mh = int(w.wallpaper.maxHeight)
|
2019-04-10 02:17:56 +00:00
|
|
|
Viewport = w.Viewport()
|
|
|
|
)
|
|
|
|
if Viewport.W > mw {
|
|
|
|
delta := Viewport.W - mw
|
|
|
|
w.Scroll.X += delta
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
if Viewport.H > mh {
|
|
|
|
delta := Viewport.H - mh
|
|
|
|
w.Scroll.Y += delta
|
|
|
|
capped = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if capped {
|
|
|
|
return errors.New("scroll limited by level constraint")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Loop() subroutine for Play Mode to follow an actor in the camera's view.
|
|
|
|
|
|
|
|
Does nothing if w.FollowActor is an empty string. Set it to the ID of an Actor
|
|
|
|
to follow. If the actor exists, the Canvas will scroll to keep it on the
|
|
|
|
screen.
|
|
|
|
*/
|
2019-12-22 22:11:01 +00:00
|
|
|
func (w *Canvas) loopFollowActor(ev *event.State) error {
|
2019-04-10 02:17:56 +00:00
|
|
|
// Are we following an actor?
|
|
|
|
if w.FollowActor == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2019-04-14 22:25:03 +00:00
|
|
|
VP = w.Viewport()
|
2019-04-10 02:17:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Find the actor.
|
|
|
|
for _, actor := range w.actors {
|
|
|
|
if actor.ID() != w.FollowActor {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2019-04-14 22:25:03 +00:00
|
|
|
APosition = actor.Position() // absolute world position
|
2019-04-10 02:17:56 +00:00
|
|
|
ASize = actor.Drawing.Size()
|
|
|
|
scrollBy render.Point
|
|
|
|
)
|
|
|
|
|
|
|
|
// Scroll left
|
2020-01-03 04:23:27 +00:00
|
|
|
if APosition.X <= VP.X+balance.ScrollboxHoz {
|
|
|
|
var delta = VP.X + balance.ScrollboxHoz - APosition.X
|
2019-04-10 02:17:56 +00:00
|
|
|
|
2020-01-03 04:23:27 +00:00
|
|
|
// constrain in case they're FAR OFF SCREEN so we don't flip back around
|
2019-04-10 02:17:56 +00:00
|
|
|
if delta < 0 {
|
|
|
|
delta = -delta
|
|
|
|
}
|
|
|
|
scrollBy.X = delta
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll right
|
2019-12-28 03:16:34 +00:00
|
|
|
if APosition.X >= VP.W-ASize.W-balance.ScrollboxHoz {
|
2020-01-03 04:23:27 +00:00
|
|
|
var delta = VP.W - ASize.W - APosition.X - balance.ScrollboxHoz
|
|
|
|
scrollBy.X = delta
|
2019-04-10 02:17:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll up
|
2020-01-03 04:23:27 +00:00
|
|
|
if APosition.Y <= VP.Y+balance.ScrollboxVert {
|
|
|
|
var delta = VP.Y + balance.ScrollboxVert - APosition.Y
|
2019-04-19 05:02:59 +00:00
|
|
|
|
2019-04-10 02:17:56 +00:00
|
|
|
if delta < 0 {
|
|
|
|
delta = -delta
|
|
|
|
}
|
|
|
|
scrollBy.Y = delta
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scroll down
|
2019-12-28 03:16:34 +00:00
|
|
|
if APosition.Y >= VP.H-ASize.H-balance.ScrollboxVert {
|
2020-01-03 04:23:27 +00:00
|
|
|
var delta = VP.H - ASize.H - APosition.Y - balance.ScrollboxVert
|
|
|
|
if delta > 300 {
|
|
|
|
delta = 300
|
|
|
|
} else if delta < -300 {
|
|
|
|
delta = -300
|
2019-04-10 02:17:56 +00:00
|
|
|
}
|
2020-01-03 04:23:27 +00:00
|
|
|
scrollBy.Y = delta
|
2019-04-10 02:17:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if scrollBy != render.Origin {
|
|
|
|
w.ScrollBy(scrollBy)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("actor ID '%s' not found in level", w.FollowActor)
|
|
|
|
}
|