summaryrefslogtreecommitdiff
path: root/mapwidget.go
diff options
context:
space:
mode:
Diffstat (limited to 'mapwidget.go')
-rw-r--r--mapwidget.go321
1 files changed, 321 insertions, 0 deletions
diff --git a/mapwidget.go b/mapwidget.go
new file mode 100644
index 0000000..764e41e
--- /dev/null
+++ b/mapwidget.go
@@ -0,0 +1,321 @@
+package main
+
+import (
+ "fmt"
+ "github.com/kch42/gomcmap/mcmap"
+ "github.com/mattn/go-gtk/gdk"
+ "github.com/mattn/go-gtk/glib"
+ "github.com/mattn/go-gtk/gtk"
+ "math"
+ "unsafe"
+)
+
+const (
+ zoom = 2
+ tileSize = zoom * mcmap.ChunkSizeXZ
+ halfChunkSize = mcmap.ChunkSizeXZ / 2
+)
+
+type tileCmd int
+
+const (
+ cmdUpdateTiles tileCmd = iota
+ cmdFlushTiles
+ cmdSave
+)
+
+func renderTile(chunk *mcmap.Chunk) (maptile, biotile *gdk.Pixmap) {
+ maptile = emptyPixmap(tileSize, tileSize, 24)
+ mtDrawable := maptile.GetDrawable()
+ mtGC := gdk.NewGC(mtDrawable)
+
+ biotile = emptyPixmap(tileSize, tileSize, 24)
+ btDrawable := biotile.GetDrawable()
+ btGC := gdk.NewGC(btDrawable)
+
+ for z := 0; z < mcmap.ChunkSizeXZ; z++ {
+ scanX:
+ for x := 0; x < mcmap.ChunkSizeXZ; x++ {
+ btGC.SetRgbFgColor(bioColors[chunk.Biome(x, z)])
+ btDrawable.DrawRectangle(btGC, true, x*zoom, z*zoom, zoom, zoom)
+
+ for y := chunk.Height(x, z); y >= 0; y-- {
+ if col, ok := blockColors[chunk.Block(x, y, z).ID]; ok {
+ mtGC.SetRgbFgColor(col)
+ mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom)
+ continue scanX
+ }
+ }
+
+ mtGC.SetRgbFgColor(gdk.NewColor("#ffffff"))
+ mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom)
+ }
+ }
+
+ return
+}
+
+type MapWidget struct {
+ dArea *gtk.DrawingArea
+ w, h int
+
+ reportFail func(msg string)
+
+ isInit bool
+
+ showBiomes bool
+
+ offX, offZ int
+ mx1, mx2, my1, my2 int
+
+ pixmap *gdk.Pixmap
+ pixmapGC *gdk.GC
+ gdkwin *gdk.Window
+
+ bg *gdk.Pixmap
+
+ region *mcmap.Region
+
+ maptiles map[XZPos]*gdk.Pixmap
+ biotiles map[XZPos]*gdk.Pixmap
+
+ redraw chan bool
+ tileCmds chan tileCmd
+}
+
+var (
+ checker1 = gdk.NewColor("#222222")
+ checker2 = gdk.NewColor("#444444")
+)
+
+func emptyPixmap(w, h, depth int) *gdk.Pixmap {
+ return gdk.NewPixmap(new(gdk.Drawable), w, h, depth)
+}
+
+func (mw *MapWidget) SetShowBiomes(b bool) {
+ mw.showBiomes = b
+ mw.redraw <- true
+}
+
+func (mw *MapWidget) DArea() *gtk.DrawingArea { return mw.dArea }
+
+func (mw *MapWidget) doTileCmds() {
+ for cmd := range mw.tileCmds {
+ switch cmd {
+ case cmdSave:
+ mw.region.Save()
+ case cmdFlushTiles:
+ gdk.ThreadsEnter()
+ for _, mt := range mw.maptiles {
+ mt.Unref()
+ }
+ for _, bt := range mw.biotiles {
+ bt.Unref()
+ }
+ gdk.ThreadsLeave()
+
+ mw.maptiles = make(map[XZPos]*gdk.Pixmap)
+ mw.biotiles = make(map[XZPos]*gdk.Pixmap)
+ case cmdUpdateTiles:
+ todelete := make(map[XZPos]bool)
+
+ startX := int(math.Floor(float64(mw.offX) / tileSize))
+ startZ := int(math.Floor(float64(mw.offZ) / tileSize))
+ endX := int(math.Ceil(float64(mw.offX+mw.w) / tileSize))
+ endZ := int(math.Ceil(float64(mw.offZ+mw.h) / tileSize))
+
+ for pos := range mw.maptiles {
+ if (pos.X < startX) || (pos.Z < startZ) || (pos.X >= endX) || (pos.Z >= endZ) {
+ todelete[pos] = true
+ }
+ }
+
+ gdk.ThreadsEnter()
+ for pos := range todelete {
+ if tile, ok := mw.maptiles[pos]; ok {
+ tile.Unref()
+ delete(mw.maptiles, pos)
+ }
+
+ if tile, ok := mw.biotiles[pos]; ok {
+ tile.Unref()
+ delete(mw.biotiles, pos)
+ }
+ }
+
+ for z := startZ; z < endZ; z++ {
+ scanX:
+ for x := startX; x < endX; x++ {
+ pos := XZPos{x, z}
+
+ if _, ok := mw.biotiles[pos]; ok {
+ continue scanX
+ }
+
+ chunk, err := mw.region.Chunk(x, z)
+ switch err {
+ case nil:
+ case mcmap.NotAvailable:
+ continue scanX
+ default:
+ mw.reportFail(fmt.Sprintf("Could not get chunk %d, %d: %s", x, z, err))
+ return
+ }
+
+ mw.maptiles[pos], mw.biotiles[pos] = renderTile(chunk)
+ chunk.MarkUnused()
+
+ gdk.ThreadsLeave()
+ mw.redraw <- true
+ gdk.ThreadsEnter()
+ }
+ }
+
+ gdk.ThreadsLeave()
+ }
+ }
+}
+
+func (mw *MapWidget) configure() {
+ if mw.pixmap != nil {
+ mw.pixmap.Unref()
+ }
+
+ alloc := mw.dArea.GetAllocation()
+ mw.w = alloc.Width
+ mw.h = alloc.Height
+
+ if !mw.isInit {
+ mw.offX = -(mw.w / 2)
+ mw.offZ = -(mw.h / 2)
+ mw.isInit = true
+ }
+
+ mw.pixmap = gdk.NewPixmap(mw.dArea.GetWindow().GetDrawable(), mw.w, mw.h, 24)
+ mw.pixmapGC = gdk.NewGC(mw.pixmap.GetDrawable())
+
+ mw.drawBg()
+ mw.compose()
+}
+
+func (mw *MapWidget) drawBg() {
+ if mw.bg != nil {
+ mw.bg.Unref()
+ }
+
+ mw.bg = emptyPixmap(mw.w, mw.h, 24)
+ drawable := mw.bg.GetDrawable()
+ gc := gdk.NewGC(drawable)
+
+ w := (mw.w + 16) / 32
+ h := (mw.h + 16) / 32
+
+ for y := 0; y < h; y++ {
+ for x := 0; x < w; x++ {
+ if (x % 2) == (y % 2) {
+ gc.SetRgbFgColor(checker1)
+ } else {
+ gc.SetRgbFgColor(checker2)
+ }
+ drawable.DrawRectangle(gc, true, x*32, y*32, 32, 32)
+ }
+ }
+}
+
+func (mw *MapWidget) compose() {
+ drawable := mw.pixmap.GetDrawable()
+ gc := mw.pixmapGC
+
+ drawable.DrawDrawable(gc, mw.bg.GetDrawable(), 0, 0, 0, 0, -1, -1)
+
+ var tiles map[XZPos]*gdk.Pixmap
+ if mw.showBiomes {
+ tiles = mw.biotiles
+ } else {
+ tiles = mw.maptiles
+ }
+
+ for pos, tile := range tiles {
+ x := (pos.X * tileSize) - mw.offX
+ y := (pos.Z * tileSize) - mw.offZ
+
+ drawable.DrawDrawable(gc, tile.GetDrawable(), 0, 0, x, y, tileSize, tileSize)
+ }
+}
+
+func (mw *MapWidget) movement(ctx *glib.CallbackContext) {
+ if mw.gdkwin == nil {
+ mw.gdkwin = mw.dArea.GetWindow()
+ }
+ arg := ctx.Args(0)
+ mev := *(**gdk.EventMotion)(unsafe.Pointer(&arg))
+ var mt gdk.ModifierType
+ if mev.IsHint != 0 {
+ mw.gdkwin.GetPointer(&(mw.mx2), &(mw.my2), &mt)
+ } else {
+ mw.mx2, mw.my2 = int(mev.X), int(mev.Y)
+ }
+
+ switch {
+ case mt&gdk.BUTTON1_MASK != 0:
+ case mt&gdk.BUTTON2_MASK != 0:
+ if (mw.mx1 != -1) && (mw.my1 != -1) {
+ mw.offX += mw.mx1 - mw.mx2
+ mw.offZ += mw.my1 - mw.my2
+
+ gdk.ThreadsLeave()
+ mw.tileCmds <- cmdUpdateTiles
+ gdk.ThreadsEnter()
+ }
+ }
+
+ mw.mx1, mw.my1 = mw.mx2, mw.my2
+}
+
+func (mw *MapWidget) expose() {
+ mw.dArea.GetWindow().GetDrawable().DrawDrawable(mw.pixmapGC, mw.pixmap.GetDrawable(), 0, 0, 0, 0, -1, -1)
+}
+
+func (mw *MapWidget) guiUpdater() {
+ for _ = range mw.redraw {
+ gdk.ThreadsEnter()
+ mw.compose()
+ mw.expose()
+ mw.dArea.GetWindow().Invalidate(nil, false)
+ gdk.ThreadsLeave()
+ }
+}
+
+func (mw *MapWidget) init() {
+ mw.redraw = make(chan bool)
+ mw.tileCmds = make(chan tileCmd)
+
+ mw.maptiles = make(map[XZPos]*gdk.Pixmap)
+ mw.biotiles = make(map[XZPos]*gdk.Pixmap)
+
+ mw.showBiomes = true
+
+ mw.mx1, mw.my1 = -1, -1
+
+ go mw.doTileCmds()
+ go mw.guiUpdater()
+
+ mw.dArea = gtk.NewDrawingArea()
+ mw.dArea.Connect("configure-event", mw.configure)
+ mw.dArea.Connect("expose-event", mw.expose)
+ mw.dArea.Connect("motion-notify-event", mw.movement)
+
+ mw.dArea.SetEvents(int(gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK | gdk.BUTTON_PRESS_MASK))
+}
+
+func (mw *MapWidget) setRegion(region *mcmap.Region) {
+ mw.tileCmds <- cmdFlushTiles
+ mw.region = region
+ mw.tileCmds <- cmdUpdateTiles
+}
+
+func NewMapWidget(reportFail func(msg string)) *MapWidget {
+ mw := &MapWidget{reportFail: reportFail}
+ mw.init()
+ return mw
+}