WIP Zoom Tool
* Added Feature Flag support, run doodle with --experimental to enable all flags. Eraser Tool is behind a feature flag now. * + and - on the top row of keyboard keys will zoom the drawing in and out in Edit Mode. The wallpaper zooms nicely enough, but level chunkers need work. * A View menu is added with Zoom in/out, reset zoom, and scroll to origin options. The whole menu is behind the Zoom feature flag. * Update README with lots of details for fun debug mode options to play around with.
This commit is contained in:
parent
24aef28a0d
commit
6e40d58010
91
README.md
91
README.md
|
@ -142,7 +142,9 @@ A brief introduction to the built-in doodads available so far:
|
||||||
|
|
||||||
# Developer Console
|
# Developer Console
|
||||||
|
|
||||||
Press `Enter` at any time to open the developer console.
|
Press `Enter` at any time to open the developer console. The console
|
||||||
|
provides commands and advanced functionality, and is also where cheat
|
||||||
|
codes can be entered.
|
||||||
|
|
||||||
Commands supported:
|
Commands supported:
|
||||||
|
|
||||||
|
@ -154,8 +156,8 @@ new
|
||||||
Show the "New Level" screen to start editing a new map.
|
Show the "New Level" screen to start editing a new map.
|
||||||
|
|
||||||
save [filename]
|
save [filename]
|
||||||
Save the current map in Edit Mode. The filename is required if the map has
|
Save the current map in Edit Mode. The filename is required
|
||||||
not been saved yet.
|
if the map has not been saved yet.
|
||||||
|
|
||||||
edit [filename]
|
edit [filename]
|
||||||
Open a map or doodad in Edit Mode.
|
Open a map or doodad in Edit Mode.
|
||||||
|
@ -166,14 +168,39 @@ play [filename]
|
||||||
echo <text>
|
echo <text>
|
||||||
Flash a message to the console.
|
Flash a message to the console.
|
||||||
|
|
||||||
|
alert <text>
|
||||||
|
Test an alert box modal with a custom message.
|
||||||
|
|
||||||
clear
|
clear
|
||||||
Clear the console output history.
|
Clear the console output history.
|
||||||
|
|
||||||
exit
|
exit
|
||||||
quit
|
quit
|
||||||
Close the developer console.
|
Close the developer console.
|
||||||
|
|
||||||
|
boolProp <property> <true/false>
|
||||||
|
Toggle certain boolean settings in the game. Most of these
|
||||||
|
are debugging related. `boolProp list` shows the available
|
||||||
|
props.
|
||||||
|
|
||||||
|
eval <expression>
|
||||||
|
$ <expression>
|
||||||
|
Execute a line of JavaScript code in the console. Several
|
||||||
|
of the game's core data types are available here; `d` is
|
||||||
|
the master game struct; d.Scene is the pointer to the
|
||||||
|
current scene. d.Scene.UI.Canvas may point to the level edit
|
||||||
|
canvas in Editor Mode. Object.keys() can enumerate public
|
||||||
|
functions and variables.
|
||||||
|
|
||||||
|
repl
|
||||||
|
Enters an interactive JavaScript shell, where the console
|
||||||
|
stays open and pre-fills a $ prompt for subsequent commands.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The JavaScript console is a feature for advanced users and was
|
||||||
|
used while developing the game. Cool things you can do with it
|
||||||
|
may be documented elsewhere.
|
||||||
|
|
||||||
## Cheat Codes
|
## Cheat Codes
|
||||||
|
|
||||||
The following cheats can be entered into the developer console.
|
The following cheats can be entered into the developer console.
|
||||||
|
@ -205,6 +232,64 @@ Experimental:
|
||||||
The player character must always remain on screen though so you can't
|
The player character must always remain on screen though so you can't
|
||||||
scroll too far away.
|
scroll too far away.
|
||||||
|
|
||||||
|
Unsupported shell commands (here be dragons):
|
||||||
|
|
||||||
|
* `reload`: reloads the current 'scene' within the game engine, using the
|
||||||
|
existing scene's data. If playing a level this will start the level over.
|
||||||
|
If editing a level this will reload the editor, but your recent unsaved
|
||||||
|
changes _should_ be left intact.
|
||||||
|
* `guitest`: loads the GUI Test scene within the game. This was where I
|
||||||
|
was testing UI widgets early on; not well maintained; the `close`
|
||||||
|
command can get you out of it.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
To enable certain debug features or customize some aspects of the game,
|
||||||
|
run it with environment variables like the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Draw a semi-transparent yellow background over all level chunks
|
||||||
|
$ DEBUG_CHUNK_COLOR=FFFF0066 ./doodle
|
||||||
|
|
||||||
|
# Set a window size for the application
|
||||||
|
# (equivalent to: doodle --window 1024x768)
|
||||||
|
$ DOODLE_W=1024 DOODLE_H=768 ./doodle
|
||||||
|
|
||||||
|
# Turn on lots of fun debug features.
|
||||||
|
$ DEBUG_CANVAS_LABEL=1 DEBUG_CHUNK_COLOR=FFFF00AA \
|
||||||
|
DEBUG_CANVAS_BORDER=FF0 ./doodle
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported variables include:
|
||||||
|
|
||||||
|
* `DOODLE_W` and `DOODLE_H` set the width and height of the application
|
||||||
|
window. Equivalent to the `--window` command-line option.
|
||||||
|
* `D_SCROLL_SPEED` (int): tune the canvas scrolling speed. Default might
|
||||||
|
be around 8 or so.
|
||||||
|
* `D_DOODAD_SIZE` (int): default size for newly created doodads
|
||||||
|
* `D_SHELL_BG` (color): set the background color of the developer console
|
||||||
|
* `D_SHELL_FG` (color): text color for the developer console
|
||||||
|
* `D_SHELL_PC` (color): color for the shell prompt text
|
||||||
|
* `D_SHELL_LN` (int): set the number of lines of output history the
|
||||||
|
console will show. This dictates how 'tall' it rises from the bottom
|
||||||
|
of the screen. Large values will cover the entire screen with console
|
||||||
|
whenever the shell is open.
|
||||||
|
* `D_SHELL_FS` (int): set the font size for the developer shell. Default
|
||||||
|
is about 16. This also affects the size of "flashed" text that appears
|
||||||
|
at the bottom of the screen.
|
||||||
|
* `DEBUG_CHUNK_COLOR` (color): set a background color over each chunk
|
||||||
|
of drawing (level or doodad). A solid color will completely block out
|
||||||
|
the wallpaper; semitransparent is best.
|
||||||
|
* `DEBUG_CANVAS_BORDER` (color): the game will draw an insert colored
|
||||||
|
border around every "Canvas" widget (drawing) on the screen. The level
|
||||||
|
itself is a Canvas and every individual Doodad or actor in the level is
|
||||||
|
its own Canvas.
|
||||||
|
* `DEBUG_CANVAS_LABEL` (bool): draws a text label over every Canvas
|
||||||
|
widget on the screen, showing its name or Actor ID and some properties,
|
||||||
|
such as Level Position (LP) and World Position (WP) of actors within
|
||||||
|
a level. LP is their placement in the level file and WP is their
|
||||||
|
actual position now (in case it moves).
|
||||||
|
|
||||||
# Author
|
# Author
|
||||||
|
|
||||||
Copyright (C) 2020 Noah Petherbridge. All rights reserved.
|
Copyright (C) 2020 Noah Petherbridge. All rights reserved.
|
||||||
|
|
|
@ -77,6 +77,10 @@ func main() {
|
||||||
Name: "guitest",
|
Name: "guitest",
|
||||||
Usage: "enter the GUI Test scene on startup",
|
Usage: "enter the GUI Test scene on startup",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "experimental",
|
||||||
|
Usage: "enable experimental Feature Flags",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Action = func(c *cli.Context) error {
|
app.Action = func(c *cli.Context) error {
|
||||||
|
@ -92,6 +96,11 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable feature flags?
|
||||||
|
if c.Bool("experimental") {
|
||||||
|
balance.FeaturesOn()
|
||||||
|
}
|
||||||
|
|
||||||
// SDL engine.
|
// SDL engine.
|
||||||
engine := sdl.New(
|
engine := sdl.New(
|
||||||
fmt.Sprintf("%s v%s", branding.AppName, branding.Version),
|
fmt.Sprintf("%s v%s", branding.AppName, branding.Version),
|
||||||
|
|
15
pkg/balance/feature_flags.go
Normal file
15
pkg/balance/feature_flags.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package balance
|
||||||
|
|
||||||
|
// Feature Flags to turn on/off experimental content.
|
||||||
|
var Feature = feature{
|
||||||
|
Zoom: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeaturesOn turns on all feature flags, from CLI --experimental option.
|
||||||
|
func FeaturesOn() {
|
||||||
|
Feature.Zoom = true
|
||||||
|
}
|
||||||
|
|
||||||
|
type feature struct {
|
||||||
|
Zoom bool
|
||||||
|
}
|
|
@ -137,7 +137,11 @@ func (d *Doodle) Run() error {
|
||||||
} else {
|
} else {
|
||||||
// Global event handlers.
|
// Global event handlers.
|
||||||
if keybind.Shutdown(ev) {
|
if keybind.Shutdown(ev) {
|
||||||
d.ConfirmExit()
|
if d.Debug { // fast exit in -debug mode.
|
||||||
|
d.running = false
|
||||||
|
} else {
|
||||||
|
d.ConfirmExit()
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -202,6 +202,23 @@ func (s *EditorScene) Loop(d *Doodle, ev *event.State) error {
|
||||||
s.UI.Canvas.RedoStroke()
|
s.UI.Canvas.RedoStroke()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom in/out.
|
||||||
|
if balance.Feature.Zoom {
|
||||||
|
if keybind.ZoomIn(ev) {
|
||||||
|
d.Flash("Zoom in")
|
||||||
|
s.UI.Canvas.Zoom++
|
||||||
|
} else if keybind.ZoomOut(ev) {
|
||||||
|
d.Flash("Zoom out")
|
||||||
|
s.UI.Canvas.Zoom--
|
||||||
|
} else if keybind.ZoomReset(ev) {
|
||||||
|
d.Flash("Reset zoom")
|
||||||
|
s.UI.Canvas.Zoom = 0
|
||||||
|
} else if keybind.Origin(ev) {
|
||||||
|
d.Flash("Scrolled back to level origin (0,0)")
|
||||||
|
s.UI.Canvas.ScrollTo(render.Origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
s.UI.Loop(ev)
|
s.UI.Loop(ev)
|
||||||
|
|
||||||
// Switching to Play Mode?
|
// Switching to Play Mode?
|
||||||
|
|
|
@ -547,26 +547,43 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
|
||||||
editMenu.AddItemAccel("Redo", "Ctrl-Y", func() {
|
editMenu.AddItemAccel("Redo", "Ctrl-Y", func() {
|
||||||
u.Canvas.RedoStroke()
|
u.Canvas.RedoStroke()
|
||||||
})
|
})
|
||||||
editMenu.AddSeparator()
|
|
||||||
editMenu.AddItem("Level options", func() {
|
|
||||||
log.Info("Opening the window")
|
|
||||||
|
|
||||||
// Open the New Level window in edit-settings mode.
|
|
||||||
u.levelSettingsWindow.Hide()
|
|
||||||
u.levelSettingsWindow = nil
|
|
||||||
u.SetupPopups(u.d)
|
|
||||||
u.levelSettingsWindow.Show()
|
|
||||||
})
|
|
||||||
|
|
||||||
////////
|
////////
|
||||||
// Level menu
|
// Level menu
|
||||||
if u.Scene.DrawingType == enum.LevelDrawing {
|
if u.Scene.DrawingType == enum.LevelDrawing {
|
||||||
levelMenu := menu.AddMenu("Level")
|
levelMenu := menu.AddMenu("Level")
|
||||||
|
levelMenu.AddItem("Page settings", func() {
|
||||||
|
log.Info("Opening the window")
|
||||||
|
|
||||||
|
// Open the New Level window in edit-settings mode.
|
||||||
|
u.levelSettingsWindow.Hide()
|
||||||
|
u.levelSettingsWindow = nil
|
||||||
|
u.SetupPopups(u.d)
|
||||||
|
u.levelSettingsWindow.Show()
|
||||||
|
})
|
||||||
levelMenu.AddItemAccel("Playtest", "P", func() {
|
levelMenu.AddItemAccel("Playtest", "P", func() {
|
||||||
u.Scene.Playtest()
|
u.Scene.Playtest()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////
|
||||||
|
// View menu
|
||||||
|
if balance.Feature.Zoom {
|
||||||
|
viewMenu := menu.AddMenu("View")
|
||||||
|
viewMenu.AddItemAccel("Zoom in", "+", func() {
|
||||||
|
u.Canvas.Zoom++
|
||||||
|
})
|
||||||
|
viewMenu.AddItemAccel("Zoom out", "-", func() {
|
||||||
|
u.Canvas.Zoom--
|
||||||
|
})
|
||||||
|
viewMenu.AddItemAccel("Reset zoom", "1", func() {
|
||||||
|
u.Canvas.Zoom = 0
|
||||||
|
})
|
||||||
|
viewMenu.AddItemAccel("Scroll drawing to origin", "0", func() {
|
||||||
|
u.Canvas.ScrollTo(render.Origin)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
////////
|
////////
|
||||||
// Tools menu
|
// Tools menu
|
||||||
toolMenu := menu.AddMenu("Tools")
|
toolMenu := menu.AddMenu("Tools")
|
||||||
|
|
|
@ -45,6 +45,26 @@ func Redo(ev *event.State) bool {
|
||||||
return ev.Ctrl && ev.KeyDown("y")
|
return ev.Ctrl && ev.KeyDown("y")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ZoomIn (+)
|
||||||
|
func ZoomIn(ev *event.State) bool {
|
||||||
|
return ev.KeyDown("=") || ev.KeyDown("+")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoomOut (-)
|
||||||
|
func ZoomOut(ev *event.State) bool {
|
||||||
|
return ev.KeyDown("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZoomReset (1)
|
||||||
|
func ZoomReset(ev *event.State) bool {
|
||||||
|
return ev.KeyDown("1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin (0) -- scrolls the canvas back to 0,0 in Editor Mode.
|
||||||
|
func Origin(ev *event.State) bool {
|
||||||
|
return ev.KeyDown("0")
|
||||||
|
}
|
||||||
|
|
||||||
// GotoPlay (P) play tests the current level in the editor.
|
// GotoPlay (P) play tests the current level in the editor.
|
||||||
func GotoPlay(ev *event.State) bool {
|
func GotoPlay(ev *event.State) bool {
|
||||||
return ev.KeyDown("p")
|
return ev.KeyDown("p")
|
||||||
|
|
|
@ -29,6 +29,7 @@ type Canvas struct {
|
||||||
// NewCanvas() with editable=true, they are both enabled.
|
// NewCanvas() with editable=true, they are both enabled.
|
||||||
Editable bool // Clicking will edit pixels of this canvas.
|
Editable bool // Clicking will edit pixels of this canvas.
|
||||||
Scrollable bool // Cursor keys will scroll the viewport of this canvas.
|
Scrollable bool // Cursor keys will scroll the viewport of this canvas.
|
||||||
|
Zoom int // Zoom level on the canvas.
|
||||||
|
|
||||||
// Selected draw tool/mode, default Pencil, for editable canvases.
|
// Selected draw tool/mode, default Pencil, for editable canvases.
|
||||||
Tool drawtool.Tool
|
Tool drawtool.Tool
|
||||||
|
@ -289,10 +290,17 @@ func (w *Canvas) ViewportRelative() render.Rect {
|
||||||
// the mouse cursor.
|
// the mouse cursor.
|
||||||
func (w *Canvas) WorldIndexAt(screenPixel render.Point) render.Point {
|
func (w *Canvas) WorldIndexAt(screenPixel render.Point) render.Point {
|
||||||
var P = ui.AbsolutePosition(w)
|
var P = ui.AbsolutePosition(w)
|
||||||
return render.Point{
|
world := render.Point{
|
||||||
X: screenPixel.X - P.X - w.Scroll.X,
|
X: screenPixel.X - P.X - w.Scroll.X,
|
||||||
Y: screenPixel.Y - P.Y - w.Scroll.Y,
|
Y: screenPixel.Y - P.Y - w.Scroll.Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Zoomies
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
world.X = w.ZoomMultiply(world.X)
|
||||||
|
world.Y = w.ZoomMultiply(world.Y)
|
||||||
|
}
|
||||||
|
return world
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chunker returns the underlying Chunker object.
|
// Chunker returns the underlying Chunker object.
|
||||||
|
|
|
@ -28,6 +28,10 @@ func (w *Canvas) commitStroke(tool drawtool.Tool, addHistory bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom the stroke coordinates (this modifies the pointer)
|
||||||
|
zStroke := w.ZoomStroke(w.currentStroke)
|
||||||
|
_ = zStroke
|
||||||
|
|
||||||
// Mark the canvas as modified.
|
// Mark the canvas as modified.
|
||||||
w.modified = true
|
w.modified = true
|
||||||
|
|
||||||
|
|
|
@ -42,9 +42,16 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
||||||
} else {
|
} else {
|
||||||
tex = chunk.Texture(e)
|
tex = chunk.Texture(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom in the texture.
|
||||||
|
texSize := tex.Size()
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
texSize.W = w.ZoomMultiply(texSize.W)
|
||||||
|
texSize.H = w.ZoomMultiply(texSize.H)
|
||||||
|
}
|
||||||
src := render.Rect{
|
src := render.Rect{
|
||||||
W: tex.Size().W,
|
W: texSize.W,
|
||||||
H: tex.Size().H,
|
H: texSize.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the source bitmap is already bigger than the Canvas widget
|
// If the source bitmap is already bigger than the Canvas widget
|
||||||
|
@ -69,6 +76,16 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
||||||
H: src.H,
|
H: src.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom the destination rect.
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
// dst.X += int(w.GetZoomMultiplier())
|
||||||
|
// dst.Y += int(w.GetZoomMultiplier())
|
||||||
|
// dst.X = w.ZoomMultiply(dst.X)
|
||||||
|
// dst.Y = w.ZoomMultiply(dst.Y)
|
||||||
|
// dst.W = w.ZoomMultiply(dst.W)
|
||||||
|
// dst.H = w.ZoomMultiply(dst.H)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: all this shit is in TrimBox(), make it DRY
|
// TODO: all this shit is in TrimBox(), make it DRY
|
||||||
|
|
||||||
// If the destination width will cause it to overflow the widget
|
// If the destination width will cause it to overflow the widget
|
||||||
|
|
|
@ -2,6 +2,7 @@ package uix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.kirsle.net/apps/doodle/pkg/level"
|
"git.kirsle.net/apps/doodle/pkg/level"
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/log"
|
||||||
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
"git.kirsle.net/apps/doodle/pkg/wallpaper"
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
)
|
)
|
||||||
|
@ -70,45 +71,96 @@ func (w *Canvas) loopContainActorsInsideLevel(a *Actor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PresentWallpaper draws the wallpaper.
|
// PresentWallpaper draws the wallpaper.
|
||||||
|
// Point p is the one given to Canvas.Present(), i.e., the position of the
|
||||||
|
// top-left corner of the Canvas widget relative to the application window.
|
||||||
func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
var (
|
var (
|
||||||
wp = w.wallpaper
|
wp = w.wallpaper
|
||||||
S = w.Size()
|
S = w.Size()
|
||||||
size = wp.corner.Size()
|
size = wp.corner.Size()
|
||||||
|
|
||||||
|
// Get the relative viewport of world coordinates looked at by the canvas.
|
||||||
|
// The X,Y values are the negative Scroll value
|
||||||
|
// The W,H values are the Canvas size same as var S above.
|
||||||
Viewport = w.ViewportRelative()
|
Viewport = w.ViewportRelative()
|
||||||
origin = render.Point{
|
|
||||||
X: p.X + w.Scroll.X + w.BoxThickness(1),
|
// origin and limit seem to be the boundaries of where on screen
|
||||||
Y: p.Y + w.Scroll.Y + w.BoxThickness(1),
|
// we are rendering inside.
|
||||||
}
|
origin = render.Point{
|
||||||
limit = render.Point{
|
X: p.X + w.Scroll.X, // + w.BoxThickness(1),
|
||||||
// NOTE: we add + the texture size so we would actually draw one
|
Y: p.Y + w.Scroll.Y, // + w.BoxThickness(1),
|
||||||
// full extra texture out-of-bounds for the repeating backgrounds.
|
|
||||||
// This is cuz for scrolling we offset the draw spot on a loop.
|
|
||||||
X: origin.X + S.W - w.BoxThickness(1) + size.W,
|
|
||||||
Y: origin.Y + S.H - w.BoxThickness(1) + size.H,
|
|
||||||
}
|
}
|
||||||
|
limit render.Point // TBD later
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Grow or shrink the render limit if we're zoomed.
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
// I was surprised to discover that just zooming the texture
|
||||||
|
// quadrant size handled most of the problem! For reference, the
|
||||||
|
// Blueprint wallpaper has a size of 120x120 for the tiling pattern.
|
||||||
|
size.H = w.ZoomMultiply(size.H)
|
||||||
|
size.W = w.ZoomMultiply(size.W)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCRATCH
|
||||||
|
// at bootup, scroll position 0,0:
|
||||||
|
// origin=44,20 p=44,20 p=relative to application window
|
||||||
|
// scroll right and down to -60,-60:
|
||||||
|
// origin=-16,-40 p=44,20 and looks good in that direction
|
||||||
|
// scroll left and up to 60,60:
|
||||||
|
// origin=104,80 p=44,20
|
||||||
|
// becomes origin=44,20 p=44,20 d=-16,-40
|
||||||
|
// the latter case is handled below. walking thru:
|
||||||
|
// if o(104) > p(44):
|
||||||
|
// while o(104) > p(44):
|
||||||
|
// o -= size(120) of texture block
|
||||||
|
// o is now -16,-40
|
||||||
|
// while o(-16) > p(44): it's not; break
|
||||||
|
// dx = o(-16)
|
||||||
|
// origin.X = p.X
|
||||||
|
// (becomes origin=44,20 p=44,20 d=-16,-40)
|
||||||
|
//
|
||||||
|
// The visual bug is: if you scroll left or up on an Unbounded level from
|
||||||
|
// the origin (0, 0), the tiling of the wallpaper jumps to the right and
|
||||||
|
// down by an offset of 44x20 pixels.
|
||||||
|
//
|
||||||
|
// what is meant to happen:
|
||||||
|
// -
|
||||||
|
|
||||||
// For tiled textures, compute the offset amount. If we are scrolled away
|
// For tiled textures, compute the offset amount. If we are scrolled away
|
||||||
// from the Origin (0,0) we find out by how far (subtract full tile sizes)
|
// from the Origin (0,0) we find out by how far (subtract full tile sizes)
|
||||||
// and use the remainder as an offset for drawing the tiles.
|
// and use the remainder as an offset for drawing the tiles.
|
||||||
|
// p = position on screen of the Canvas widget
|
||||||
|
// origin = p.X + Scroll.X, p.Y + scroll.Y
|
||||||
|
// note: negative Scroll values means to the right and down
|
||||||
var dx, dy int
|
var dx, dy int
|
||||||
if origin.X > p.X {
|
if origin.X > p.X {
|
||||||
for origin.X > p.X && origin.X > size.W {
|
// View is scrolled leftward (into negative world coordinates)
|
||||||
origin.X -= size.W
|
|
||||||
}
|
|
||||||
dx = origin.X
|
dx = origin.X
|
||||||
origin.X = p.X
|
for dx > p.X {
|
||||||
|
dx -= size.W
|
||||||
|
}
|
||||||
|
origin.X = 0 // note: origin 0,0 will be the corner of the app window
|
||||||
}
|
}
|
||||||
if origin.Y > p.Y {
|
if origin.Y > p.Y {
|
||||||
for origin.Y > p.Y && origin.Y > size.H {
|
// View is scrolled upward (into negative world coordinates)
|
||||||
origin.Y -= size.H
|
|
||||||
}
|
|
||||||
dy = origin.Y
|
dy = origin.Y
|
||||||
origin.Y = p.Y
|
for dy > p.Y {
|
||||||
|
dy -= size.H
|
||||||
|
}
|
||||||
|
origin.Y = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// And capping the scroll delta in the other direction.
|
limit = render.Point{
|
||||||
|
// NOTE: we add + the texture size so we would actually draw one
|
||||||
|
// full extra texture out-of-bounds for the repeating backgrounds.
|
||||||
|
// This is cuz for scrolling we offset the draw spot on a loop.
|
||||||
|
X: origin.X + S.W + size.W,
|
||||||
|
Y: origin.Y + S.H + size.H,
|
||||||
|
}
|
||||||
|
|
||||||
|
// And capping the scroll delta in the other direction. Always draw
|
||||||
|
// pixels until the Canvas size is covered.
|
||||||
if limit.X < S.W {
|
if limit.X < S.W {
|
||||||
limit.X = S.W
|
limit.X = S.W
|
||||||
}
|
}
|
||||||
|
@ -117,10 +169,12 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
limit.Y = S.H
|
limit.Y = S.H
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: was still getting some slight flicker on the right and bottom
|
||||||
|
// when scrolling.. add a bit extra margin.
|
||||||
limit.X += size.W
|
limit.X += size.W
|
||||||
limit.Y += size.H
|
limit.Y += size.H
|
||||||
|
|
||||||
// Tile the repeat texture.
|
// Tile the repeat texture. Start from 1 full wallpaper tile out of bounds
|
||||||
for x := origin.X - size.W; x < limit.X; x += size.W {
|
for x := origin.X - size.W; x < limit.X; x += size.W {
|
||||||
for y := origin.Y - size.H; y < limit.Y; y += size.H {
|
for y := origin.Y - size.H; y < limit.Y; y += size.H {
|
||||||
src := render.Rect{
|
src := render.Rect{
|
||||||
|
@ -134,8 +188,20 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
H: src.H,
|
H: src.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom the output texture.
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
// dst.X = w.ZoomMultiply(dst.X - p.X)
|
||||||
|
// dst.Y = w.ZoomMultiply(dst.Y - p.Y)
|
||||||
|
// dst.W = w.ZoomMultiply(dst.W)
|
||||||
|
// dst.H = w.ZoomMultiply(dst.H)
|
||||||
|
}
|
||||||
|
|
||||||
// Trim the edges of the destination box, like in canvas.go#Present
|
// Trim the edges of the destination box, like in canvas.go#Present
|
||||||
|
odst := dst
|
||||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
|
if dst.W == 0 {
|
||||||
|
log.Error("TrimBoxed! %s => %s", odst, dst)
|
||||||
|
}
|
||||||
|
|
||||||
e.Copy(wp.repeat, src, dst)
|
e.Copy(wp.repeat, src, dst)
|
||||||
}
|
}
|
||||||
|
@ -154,6 +220,15 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
W: src.W,
|
W: src.W,
|
||||||
H: src.H,
|
H: src.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom the output texture.
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
// dst.X = w.ZoomMultiply(dst.X - origin.X)
|
||||||
|
// dst.Y = w.ZoomMultiply(dst.Y - origin.Y)
|
||||||
|
// dst.W = w.ZoomMultiply(dst.W)
|
||||||
|
// dst.H = w.ZoomMultiply(dst.H)
|
||||||
|
}
|
||||||
|
|
||||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
e.Copy(wp.left, src, dst)
|
e.Copy(wp.left, src, dst)
|
||||||
}
|
}
|
||||||
|
@ -170,6 +245,15 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
W: src.W,
|
W: src.W,
|
||||||
H: src.H,
|
H: src.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom the output texture.
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
// dst.X = w.ZoomMultiply(dst.X - origin.X)
|
||||||
|
// dst.Y = w.ZoomMultiply(dst.Y - origin.Y)
|
||||||
|
// dst.W = w.ZoomMultiply(dst.W)
|
||||||
|
// dst.H = w.ZoomMultiply(dst.H)
|
||||||
|
}
|
||||||
|
|
||||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
e.Copy(wp.top, src, dst)
|
e.Copy(wp.top, src, dst)
|
||||||
}
|
}
|
||||||
|
@ -186,6 +270,15 @@ func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
|
||||||
W: src.W,
|
W: src.W,
|
||||||
H: src.H,
|
H: src.H,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zoom the output texture.
|
||||||
|
if w.Zoom != 0 {
|
||||||
|
// dst.X = w.ZoomMultiply(dst.X - origin.X)
|
||||||
|
// dst.Y = w.ZoomMultiply(dst.Y - origin.Y)
|
||||||
|
// dst.W = w.ZoomMultiply(dst.W)
|
||||||
|
// dst.H = w.ZoomMultiply(dst.H)
|
||||||
|
}
|
||||||
|
|
||||||
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
|
||||||
e.Copy(wp.corner, src, dst)
|
e.Copy(wp.corner, src, dst)
|
||||||
}
|
}
|
||||||
|
|
97
pkg/uix/canvas_zoom.go
Normal file
97
pkg/uix/canvas_zoom.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package uix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Functions related to the Zoom Tool to magnify the size of the canvas.
|
||||||
|
|
||||||
|
/*
|
||||||
|
GetZoomMultiplier parses the .Zoom integer and returns a multiplier.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
Zoom = 0: neutral (100% scale, 1x)
|
||||||
|
Zoom = 1: 2x zoom
|
||||||
|
Zoom = 2: 4x zoom
|
||||||
|
Zoom = 3: 8x zoom
|
||||||
|
Zoom = -1: 0.5x zoom
|
||||||
|
Zoom = -2: 0.25x zoom
|
||||||
|
*/
|
||||||
|
func (w *Canvas) GetZoomMultiplier() float64 {
|
||||||
|
// Get and bounds cap the zoom setting.
|
||||||
|
if w.Zoom < -2 {
|
||||||
|
w.Zoom = -2
|
||||||
|
} else if w.Zoom > 3 {
|
||||||
|
w.Zoom = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the multipliers.
|
||||||
|
switch w.Zoom {
|
||||||
|
case -2:
|
||||||
|
return 0.25
|
||||||
|
case -1:
|
||||||
|
return 0.5
|
||||||
|
case 0:
|
||||||
|
return 1
|
||||||
|
case 1:
|
||||||
|
return 1.5
|
||||||
|
case 2:
|
||||||
|
return 2
|
||||||
|
case 3:
|
||||||
|
return 2.5
|
||||||
|
default:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ZoomMultiply multiplies a width or height value by the Zoom Multiplier and
|
||||||
|
returns the modified integer.
|
||||||
|
|
||||||
|
Usage is like:
|
||||||
|
|
||||||
|
// when building a render.Rect destination box.
|
||||||
|
dest.W *= ZoomMultiply(dest.W)
|
||||||
|
dest.H *= ZoomMultiply(dest.H)
|
||||||
|
*/
|
||||||
|
func (w *Canvas) ZoomMultiply(value int) int {
|
||||||
|
return int(float64(value) * w.GetZoomMultiplier())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ZoomStroke adjusts a drawn stroke on the canvas to account for the zoom level.
|
||||||
|
|
||||||
|
Returns a copy Stroke value without changing the original.
|
||||||
|
*/
|
||||||
|
func (w *Canvas) ZoomStroke(stroke *drawtool.Stroke) drawtool.Stroke {
|
||||||
|
copy := drawtool.Stroke{
|
||||||
|
ID: stroke.ID,
|
||||||
|
Shape: stroke.Shape,
|
||||||
|
Color: stroke.Color,
|
||||||
|
Thickness: stroke.Thickness,
|
||||||
|
ExtraData: stroke.ExtraData,
|
||||||
|
PointA: stroke.PointA,
|
||||||
|
PointB: stroke.PointB,
|
||||||
|
Points: stroke.Points,
|
||||||
|
OriginalPoints: stroke.OriginalPoints,
|
||||||
|
}
|
||||||
|
return copy
|
||||||
|
|
||||||
|
// Multiply all coordinates in this stroke, which should be World
|
||||||
|
// Coordinates in the level data, by the zoom multiplier.
|
||||||
|
adjust := func(p render.Point) render.Point {
|
||||||
|
p.X = w.ZoomMultiply(p.X)
|
||||||
|
p.Y = w.ZoomMultiply(p.Y)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.PointA = adjust(copy.PointA)
|
||||||
|
copy.PointB = adjust(copy.PointB)
|
||||||
|
for i := range copy.Points {
|
||||||
|
copy.Points[i] = adjust(copy.Points[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user