Support panning around canvas

This commit is contained in:
Trevor Slocum 2021-10-22 02:20:09 -07:00
parent bc6a54f325
commit ee5ab2ed49
7 changed files with 222 additions and 38 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
.idea
*.sh
dist
edbit

View File

@ -1,15 +1,21 @@
package application
import (
"errors"
"fmt"
"image"
"image/color"
"image/gif"
"image/jpeg"
"image/png"
"log"
"math"
"os"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
"golang.org/x/image/bmp"
"golang.org/x/image/colornames"
)
@ -22,6 +28,8 @@ type Application struct {
canvases []*canvas
active int
spinnerIndex int
hoverImg *ebiten.Image
@ -30,35 +38,95 @@ type Application struct {
}
func NewApplication() *Application {
a := &Application{
app := &Application{
op: &ebiten.DrawImageOptions{},
}
a.addCanvas(8, 8)
app.addNewCanvas(8, 8)
a.canvases[0].img.Fill(colornames.Purple)
c := app.activeCanvas()
c.img.Fill(colornames.Purple)
c.scale = 256
a.activeCanvas().scale = 256
a.hoverImg = ebiten.NewImage(int(a.canvases[0].scale), int(a.canvases[0].scale)) // TODO
a.drawHoverImage()
app.drawHoverImage()
return a
return app
}
func (app *Application) addCanvas(w, h int) {
func (app *Application) readFile(p string) (image.Image, error) {
f, err := os.OpenFile(p, os.O_RDONLY, 0600)
if err != nil {
log.Fatal(err)
}
defer f.Close()
p = strings.ToLower(p)
if strings.HasSuffix(p, ".bmp") {
img, err := bmp.Decode(f)
if err != nil {
log.Fatalf("failed to decode BMP file: %s", err)
}
return img, nil
} else if strings.HasSuffix(p, ".gif") {
img, err := gif.Decode(f)
if err != nil {
log.Fatalf("failed to decode GIF file: %s", err)
}
return img, nil
} else if strings.HasSuffix(p, ".jpg") || strings.HasSuffix(p, ".jpeg") {
img, err := jpeg.Decode(f)
if err != nil {
log.Fatalf("failed to decode JPG file: %s", err)
}
return img, nil
} else if strings.HasSuffix(p, ".png") {
img, err := png.Decode(f)
if err != nil {
log.Fatalf("failed to decode PNG file: %s", err)
}
return img, nil
} else if strings.HasSuffix(p, ".tff") || strings.HasSuffix(p, ".tiff") {
img, err := png.Decode(f)
if err != nil {
log.Fatalf("failed to decode PNG file: %s", err)
}
return img, nil
}
return nil, errors.New("unknown image format")
}
func (app *Application) OpenFile(p string) {
img, err := app.readFile(p)
if err != nil {
log.Fatal(err)
}
c := NewCanvas(img.Bounds().Dx(), img.Bounds().Dy())
c.img = ebiten.NewImageFromImage(img)
app.addCanvas(c)
}
func (app *Application) addCanvas(c *canvas) {
app.canvases = append(app.canvases, c)
app.active++
app.drawHoverImage()
}
func (app *Application) addNewCanvas(w, h int) {
c := NewCanvas(w, h)
app.canvases = append(app.canvases, c)
app.drawHoverImage()
}
func (app *Application) activeCanvas() *canvas {
return app.canvases[0]
return app.canvases[app.active]
}
func (app *Application) screenToCanvas(x, y int) (int, int) {
c := app.activeCanvas()
cx, cy := int(float64(x)/c.scale), int(float64(y)/c.scale)
return cx, cy
cx, cy := float64(x)/c.scale, float64(y)/c.scale
cx, cy = cx+c.x, cy+c.y
return int(cx), int(cy)
}
func (app *Application) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) {
@ -86,20 +154,22 @@ func (app *Application) Update() error {
scroll = 1
}
offset := scroll * (app.canvases[0].scale / 7)
app.canvases[0].scale += offset
if app.canvases[0].scale < .001 {
app.canvases[0].scale = .001
} else if app.canvases[0].scale > 1000 {
app.canvases[0].scale = 1000
c := app.activeCanvas()
offset := scroll * (c.scale / 7)
c.scale += offset
if c.scale < .001 {
c.scale = .001
} else if c.scale > 1000 {
c.scale = 1000
}
hoverSize := int(math.Ceil(app.canvases[0].scale))
if hoverSize < 1 {
hoverSize = 1
if c.scale > 10 {
c.scale = float64(int(c.scale))
} else {
c.scale = math.Trunc(c.scale*10000) / 10000
}
hoverSize += 2
app.hoverImg = ebiten.NewImage(hoverSize, hoverSize)
app.drawHoverImage()
}
@ -111,29 +181,43 @@ func (app *Application) Update() error {
c.img.Set(cx, cy, colornames.White)
}
c := app.activeCanvas()
panSize := 2 / c.scale
if ebiten.IsKeyPressed(ebiten.KeyRight) {
c.x += panSize
}
if ebiten.IsKeyPressed(ebiten.KeyLeft) {
c.x -= panSize
}
if ebiten.IsKeyPressed(ebiten.KeyUp) {
c.y -= panSize
}
if ebiten.IsKeyPressed(ebiten.KeyDown) {
c.y += panSize
}
return nil
}
func (app *Application) Draw(screen *ebiten.Image) {
// TODO translate and scale by camera pos
// Draw canvas.
c := app.activeCanvas()
app.op.GeoM.Reset()
app.op.GeoM.Translate(-c.x, -c.y)
app.op.GeoM.Scale(c.scale, c.scale)
screen.DrawImage(c.img, app.op)
// Draw hover outline.
cx, cy := app.screenToCanvas(app.cursorX, app.cursorY)
p := c.pixelScreenRect(cx, cy)
app.op.GeoM.Reset()
app.op.GeoM.Translate(float64(p.Min.X)-1, float64(p.Min.Y)-1)
app.op.GeoM.Translate(float64(p.Min.X), float64(p.Min.Y))
screen.DrawImage(app.hoverImg, app.op)
// Print debug information.
debugInfo := fmt.Sprintf("SCA %0.2f\nFPS %c %0.0f", app.activeCanvas().scale, spinner[app.spinnerIndex], ebiten.CurrentFPS())
debugBox := image.NewRGBA(image.Rect(10, 20, 200, 200))
debugImg := ebiten.NewImageFromImage(debugBox)
ebitenutil.DebugPrint(debugImg, debugInfo)
app.op.GeoM.Reset()
app.op.GeoM.Translate(3, 0)
app.op.GeoM.Scale(2, 2)
@ -146,6 +230,15 @@ func (app *Application) Draw(screen *ebiten.Image) {
}
func (app *Application) drawHoverImage() {
c := app.activeCanvas()
hoverImgSize := int(c.scale)
if hoverImgSize < 1 {
hoverImgSize = 1
}
if app.hoverImg == nil || app.hoverImg.Bounds().Dx() != hoverImgSize || app.hoverImg.Bounds().Dy() != hoverImgSize {
app.hoverImg = ebiten.NewImage(hoverImgSize, hoverImgSize)
}
var colorWhite bool
nextColor := func() color.Color {
colorWhite = !colorWhite
@ -160,24 +253,24 @@ func (app *Application) drawHoverImage() {
img.Clear()
bounds := img.Bounds()
hoverSize := 2
outlineWidth := 2
if app.activeCanvas().scale >= 1000 {
hoverSize = 6
outlineWidth = 6
} else if app.activeCanvas().scale >= 200 {
hoverSize = 5
outlineWidth = 5
} else if app.activeCanvas().scale >= 100 {
hoverSize = 4
outlineWidth = 4
} else if app.activeCanvas().scale >= 10 {
hoverSize = 3
outlineWidth = 3
}
for y := 0; y < hoverSize; y++ {
for y := 0; y < outlineWidth; y++ {
for x := 0; x < bounds.Max.X; x++ {
c := nextColor()
img.Set(x, bounds.Min.Y+y, c)
img.Set(x, bounds.Max.Y-y, c)
}
}
for x := 0; x < hoverSize; x++ {
for x := 0; x < outlineWidth; x++ {
for y := 0; y < bounds.Max.Y; y++ {
c := nextColor()
img.Set(bounds.Min.X+x, y, c)

View File

@ -10,6 +10,8 @@ import (
type canvas struct {
w, h int
x, y float64 // View position
img *ebiten.Image
scale float64
@ -25,5 +27,7 @@ func NewCanvas(w, h int) *canvas {
}
func (c *canvas) pixelScreenRect(x, y int) image.Rectangle {
return image.Rect(int(math.Floor(float64(x)*c.scale)), int(math.Floor(float64(y)*c.scale)), int(math.Ceil(float64(x+1)*c.scale)), int(math.Ceil(float64(y+1)*c.scale)))
rx, ry := (float64(x)-c.x)*c.scale, (float64(y)-c.y)*c.scale
rxCorner, ryCorner := (float64(x+1)-c.x)*c.scale, (float64(y+1)-c.y)*c.scale
return image.Rect(int(math.Floor(rx)), int(math.Floor(ry)), int(math.Ceil(rxCorner)), int(math.Ceil(ryCorner)))
}

40
flags.go Normal file
View File

@ -0,0 +1,40 @@
//go:build !js && !wasm
// +build !js,!wasm
package main
import (
"flag"
"log"
"os"
"path/filepath"
"strings"
"code.rocketnine.space/tslocum/edbit/application"
)
func parseFlags(app *application.Application) {
flag.Parse()
args := flag.Args()
for _, p := range args {
if strings.TrimSpace(p) == "" {
continue
}
if p == "~" {
dir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
p = dir
} else if strings.HasPrefix(p, "~/") {
dir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
p = filepath.Join(dir, p[2:])
}
app.OpenFile(p)
}
}

12
flags_web.go Normal file
View File

@ -0,0 +1,12 @@
//go:build js || wasm
// +build js wasm
package main
import (
"code.rocketnine.space/tslocum/edbit/application"
)
func parseFlags(app *application.Application) {
// Do nothing
}

31
goreleaser.yml Normal file
View File

@ -0,0 +1,31 @@
project_name: edbit
builds:
-
id: edbit
# ldflags:
# - -s -w -X code.rocketnine.space/tslocum/edbit/main.Version={{.Version}}
goos:
- js
- linux
- windows
goarch:
- amd64
- wasm
archives:
-
id: edbit
builds:
- edbit
replacements:
386: i386
format_overrides:
- goos: js
format: zip
- goos: windows
format: zip
files:
- ./*.md
- LICENSE
checksum:
name_template: 'checksums.txt'

View File

@ -42,6 +42,8 @@ func main() {
app.Exit()
}()
parseFlags(app)
if err := ebiten.RunGame(app); err != nil {
log.Fatal(err)
}