summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--emptyPixmap.go13
-rw-r--r--main.go4
-rw-r--r--mapwidget.go549
-rw-r--r--region_wrapper.go363
4 files changed, 496 insertions, 433 deletions
diff --git a/emptyPixmap.go b/emptyPixmap.go
new file mode 100644
index 0000000..7635225
--- /dev/null
+++ b/emptyPixmap.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "github.com/mattn/go-gtk/gdk"
+)
+
+// emptyPixmap creates an empty pixmap.
+func emptyPixmap(w, h, depth int) *gdk.Pixmap {
+ // The underlying C function would create an empty, unbound pixmap, if the drawable paramater was a NULL pointer.
+ // Since simply passing a nil value would result in a panic (dereferencing a nil pointer), we use a new gdk.Drawable.
+ // gdk.Drawable contains a C pointer which is NULL by default. So passing a new(gdk.Drawable) does the trick.
+ return gdk.NewPixmap(new(gdk.Drawable), w, h, depth)
+}
diff --git a/main.go b/main.go
index 4a7481d..44c72fd 100644
--- a/main.go
+++ b/main.go
@@ -38,7 +38,7 @@ func (g *GUI) openWorld(path string) {
dlg.Destroy()
}
- go g.mapw.setRegion(region)
+ go g.mapw.SetRegion(region)
}
func (g *GUI) aboutDlg() {
@@ -209,7 +209,7 @@ func (g *GUI) Init() {
hbox := gtk.NewHBox(false, 0)
- g.mapw = NewMapWidget(g.reportError, g.updateInfo, g.setBusy)
+ g.mapw = NewMapWidget(GUICallbacks{g.reportError, g.updateInfo, g.setBusy})
hbox.PackStart(g.mapw.DArea(), true, true, 3)
sidebar := g.mkSidebar()
diff --git a/mapwidget.go b/mapwidget.go
index 0727038..e9cd97c 100644
--- a/mapwidget.go
+++ b/mapwidget.go
@@ -1,7 +1,6 @@
package main
import (
- "fmt"
"github.com/kch42/gomcmap/mcmap"
"github.com/mattn/go-gtk/gdk"
"github.com/mattn/go-gtk/glib"
@@ -11,70 +10,25 @@ import (
)
const (
- zoom = 2
- tileSize = zoom * mcmap.ChunkSizeXZ
- cacheSize = 4
+ zoom = 2
+ tileSize = zoom * mcmap.ChunkSizeXZ
)
-type tileCmd int
-
-const (
- cmdUpdateTiles tileCmd = iota
- cmdFlushTiles
- cmdSave
-)
-
-func renderTile(chunk *mcmap.Chunk) (maptile, biotile *gdk.Pixmap, biocache []mcmap.Biome) {
- maptile = emptyPixmap(tileSize, tileSize, 24)
- mtDrawable := maptile.GetDrawable()
- mtGC := gdk.NewGC(mtDrawable)
-
- biotile = emptyPixmap(tileSize, tileSize, 24)
- btDrawable := biotile.GetDrawable()
- btGC := gdk.NewGC(btDrawable)
-
- biocache = make([]mcmap.Biome, mcmap.ChunkRectXZ)
-
- i := 0
- for z := 0; z < mcmap.ChunkSizeXZ; z++ {
- scanX:
- for x := 0; x < mcmap.ChunkSizeXZ; x++ {
- bio := chunk.Biome(x, z)
- btGC.SetRgbFgColor(bioColors[bio])
- btDrawable.DrawRectangle(btGC, true, x*zoom, z*zoom, zoom, zoom)
-
- biocache[i] = bio
- i++
-
- 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 GUICallbacks struct {
+ reportFail func(msg string)
+ updateInfo func(x, z int, bio mcmap.Biome)
+ setBusy func(bool)
}
type MapWidget struct {
dArea *gtk.DrawingArea
w, h int
- reportFail func(msg string)
- updateInfo func(x, z int, bio mcmap.Biome)
- setBusy func(bool)
-
- toolsEnabled bool
+ guicbs GUICallbacks
isInit bool
- showBiomes, fixSnowIce bool
+ showBiomes bool
offX, offZ int
mx1, mx2, my1, my2 int
@@ -86,234 +40,35 @@ type MapWidget struct {
bg *gdk.Pixmap
- region *CachedRegion
-
- maptiles map[XZPos]*gdk.Pixmap
- biotiles map[XZPos]*gdk.Pixmap
- bioCache map[XZPos][]mcmap.Biome
+ regWrap *RegionWrapper
- redraw chan bool
- tileCmds chan tileCmd
-
- tool Tool
- bio mcmap.Biome
+ redraw chan bool
}
-var (
- checker1 = gdk.NewColor("#222222")
- checker2 = gdk.NewColor("#444444")
-)
+func (mw *MapWidget) calcChunkRect() {
-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) SetFixSnowIce(b bool) {
- mw.fixSnowIce = b
-}
-
-func (mw *MapWidget) SetTool(t Tool) {
- mw.tool = t
-}
-
-func (mw *MapWidget) SetBiome(bio mcmap.Biome) {
- mw.bio = bio
}
func (mw *MapWidget) DArea() *gtk.DrawingArea { return mw.dArea }
-func (mw *MapWidget) doTileCmds() {
- for cmd := range mw.tileCmds {
- switch cmd {
- case cmdSave:
- if err := mw.region.Flush(); err != nil {
- mw.reportFail(fmt.Sprintf("Error while flushing cache: %s", err))
- return
- }
- if err := mw.region.Region.Save(); err != nil {
- mw.reportFail(fmt.Sprintf("Error while saving: %s", err))
- return
- }
- case cmdFlushTiles:
- if err := mw.region.Flush(); err != nil {
- mw.reportFail(fmt.Sprintf("Error while flushing cache: %s", err))
- return
- }
-
- 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)
- mw.bioCache = make(map[XZPos][]mcmap.Biome)
- 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)
- }
-
- if _, ok := mw.bioCache[pos]; ok {
- delete(mw.bioCache, pos)
- }
- }
-
- if mw.region != nil {
- 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], mw.bioCache[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()
- gdk.ThreadsLeave()
+func (mw *MapWidget) SetShowBiomes(b bool) {
+ mw.showBiomes = b
mw.redraw <- true
- gdk.ThreadsEnter()
}
-func (mw *MapWidget) drawBg() {
- if mw.bg != nil {
- mw.bg.Unref()
- }
+func (mw *MapWidget) SetFixSnowIce(b bool) { mw.regWrap.SetFixSnowIce(b) }
+func (mw *MapWidget) SetBiome(bio mcmap.Biome) { mw.regWrap.SetBiome(bio) }
+func (mw *MapWidget) SetRegion(region *mcmap.Region) { mw.regWrap.SetRegion(region) }
+func (mw *MapWidget) SetTool(t Tool) { mw.regWrap.SetTool(t) }
- mw.bg = emptyPixmap(mw.w, mw.h, 24)
- drawable := mw.bg.GetDrawable()
- gc := gdk.NewGC(drawable)
+func (mw *MapWidget) Save() { mw.regWrap.Save() }
- w := int(math.Ceil(float64(mw.w) / 32))
- h := int(math.Ceil(float64(mw.h) / 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) useTool(x, z int) {
- gdk.ThreadsLeave()
- defer gdk.ThreadsEnter()
-
- if !mw.toolsEnabled {
- return
- }
-
- if mw.tool.IsSlow() {
- mw.toolsEnabled = false
- mw.setBusy(true)
-
- go func() {
- mw.tool.Do(mw.bio, mw, x, z)
- mw.setBusy(false)
- mw.toolsEnabled = true
-
- mw.redraw <- true
- }()
- } else {
- mw.tool.Do(mw.bio, mw, x, z)
- }
+func (mw *MapWidget) updateChunkBounds() {
+ 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))
+ mw.regWrap.SetChunkBounds(startX, startZ, endX, endZ)
}
func (mw *MapWidget) movement(ctx *glib.CallbackContext) {
@@ -331,12 +86,12 @@ func (mw *MapWidget) movement(ctx *glib.CallbackContext) {
x := (mw.offX + mw.mx2) / zoom
z := (mw.offZ + mw.my2) / zoom
- cx, cz, cbx, cbz := mcmap.BlockToChunk(x, z)
+
bio := mcmap.Biome(mcmap.BioUncalculated)
- if bc, ok := mw.bioCache[XZPos{cx, cz}]; ok {
- bio = bc[cbz*mcmap.ChunkSizeXZ+cbx]
+ if _bio, ok := mw.regWrap.GetBiomeAt(x, z); ok {
+ bio = _bio
}
- mw.updateInfo(x, z, bio)
+ mw.guicbs.updateInfo(x, z, bio)
if mw.panning {
if (mw.mx1 != -1) && (mw.my1 != -1) {
@@ -350,7 +105,7 @@ func (mw *MapWidget) movement(ctx *glib.CallbackContext) {
}
if mw.continueTool {
- mw.useTool(x, z)
+ mw.regWrap.UseTool(x, z)
gdk.ThreadsLeave()
mw.redraw <- true
@@ -360,10 +115,6 @@ func (mw *MapWidget) movement(ctx *glib.CallbackContext) {
mw.mx1, mw.my1 = mw.mx2, mw.my2
}
-func (mw *MapWidget) Save() {
- mw.tileCmds <- cmdSave
-}
-
func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) {
arg := ctx.Args(0)
bev := *(**gdk.EventButton)(unsafe.Pointer(&arg))
@@ -373,8 +124,10 @@ func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) {
if mw.panning {
mw.panning = false
+ mw.updateChunkBounds()
+
gdk.ThreadsLeave()
- mw.tileCmds <- cmdUpdateTiles
+ mw.regWrap.UpdateTiles()
gdk.ThreadsEnter()
}
@@ -382,18 +135,18 @@ func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) {
case gdk.BUTTON_PRESS:
switch bev.Button {
case 1:
- if mw.region == nil {
+ if !mw.regWrap.RegionLoaded() {
return
}
x := (mw.offX + int(bev.X)) / zoom
z := (mw.offZ + int(bev.Y)) / zoom
- mw.useTool(x, z)
+ mw.regWrap.UseTool(x, z)
gdk.ThreadsLeave()
mw.redraw <- true
gdk.ThreadsEnter()
- if !mw.tool.SingleClick() {
+ if !mw.regWrap.ToolSingleClick() {
mw.continueTool = true
}
case 2:
@@ -402,182 +155,116 @@ func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) {
}
}
-func (mw *MapWidget) expose() {
- mw.dArea.GetWindow().GetDrawable().DrawDrawable(mw.pixmapGC, mw.pixmap.GetDrawable(), 0, 0, 0, 0, -1, -1)
-}
+var (
+ checker1 = gdk.NewColor("#222222")
+ checker2 = gdk.NewColor("#444444")
+)
-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) drawBg() {
+ if mw.bg != nil {
+ mw.bg.Unref()
}
-}
-
-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.bioCache = make(map[XZPos][]mcmap.Biome)
-
- 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.Connect("button-press-event", mw.buttonChanged)
- mw.dArea.Connect("button-release-event", mw.buttonChanged)
-
- mw.dArea.SetEvents(int(gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK))
-}
-
-func (mw *MapWidget) setRegion(region *mcmap.Region) {
- mw.tileCmds <- cmdFlushTiles
- mw.region = NewCachedRegion(region, cacheSize)
- mw.tileCmds <- cmdUpdateTiles
-}
-func (mw *MapWidget) GetBiomeAt(x, z int) (mcmap.Biome, bool) {
- cx, cz, bx, bz := mcmap.BlockToChunk(x, z)
- pos := XZPos{cx, cz}
-
- if bc, ok := mw.bioCache[pos]; ok {
- return bc[bz*mcmap.ChunkSizeXZ+bx], true
- }
+ mw.bg = emptyPixmap(mw.w, mw.h, 24)
+ drawable := mw.bg.GetDrawable()
+ gc := gdk.NewGC(drawable)
- chunk, err := mw.region.Chunk(cx, cz)
- switch err {
- case nil:
- case mcmap.NotAvailable:
- return mcmap.BioUncalculated, false
- default:
- mw.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err))
- return mcmap.BioUncalculated, false
- }
+ w := int(math.Ceil(float64(mw.w) / 32))
+ h := int(math.Ceil(float64(mw.h) / 32))
- bc := make([]mcmap.Biome, mcmap.ChunkRectXZ)
- i := 0
- for z := 0; z < mcmap.ChunkSizeXZ; z++ {
- for x := 0; x < mcmap.ChunkSizeXZ; x++ {
- bc[i] = chunk.Biome(x, z)
- i++
+ 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)
}
}
- mw.bioCache[pos] = bc
-
- return chunk.Biome(bx, bz), true
}
-func fixFreeze(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) {
- for y := chunk.Height(bx, bz); y >= 0; y-- {
- if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir {
- if (blk.ID == mcmap.BlkStationaryWater) || (blk.ID == mcmap.BlkWater) {
- blk.ID = mcmap.BlkIce
- newcol = blockColors[mcmap.BlkIce]
- } else if blockCanSnowIn[blk.ID] {
- if yFix := y + 1; yFix < mcmap.ChunkSizeY {
- blkFix := chunk.Block(bx, yFix, bz)
- blkFix.ID = mcmap.BlkSnow
- blkFix.Data = 0x0
- newcol = blockColors[mcmap.BlkSnow]
- }
- }
-
- break
- }
+func (mw *MapWidget) configure() {
+ if mw.pixmap != nil {
+ mw.pixmap.Unref()
}
- return
-}
-
-func fixMelt(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) {
- for y := chunk.Height(bx, bz); y >= 0; y-- {
- if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir {
- if blk.ID == mcmap.BlkIce {
- blk.ID = mcmap.BlkStationaryWater
- blk.Data = 0x0
- newcol = blockColors[mcmap.BlkStationaryWater]
- } else if blk.ID == mcmap.BlkSnow {
- blk.ID = mcmap.BlkAir
- for y2 := y - 1; y2 >= 0; y2-- {
- if col, ok := blockColors[chunk.Block(bx, y2, bz).ID]; ok {
- newcol = col
- break
- }
- }
- }
+ alloc := mw.dArea.GetAllocation()
+ mw.w = alloc.Width
+ mw.h = alloc.Height
- break
- }
+ if !mw.isInit {
+ mw.offX = -(mw.w / 2)
+ mw.offZ = -(mw.h / 2)
+ mw.updateChunkBounds()
+ mw.isInit = true
}
- return
+ mw.pixmap = gdk.NewPixmap(mw.dArea.GetWindow().GetDrawable(), mw.w, mw.h, 24)
+ mw.pixmapGC = gdk.NewGC(mw.pixmap.GetDrawable())
+
+ mw.drawBg()
+ gdk.ThreadsLeave()
+ mw.redraw <- true
+ gdk.ThreadsEnter()
}
-func (mw *MapWidget) SetBiomeAt(x, z int, bio mcmap.Biome) {
- cx, cz, bx, bz := mcmap.BlockToChunk(x, z)
- pos := XZPos{cx, cz}
-
- chunk, err := mw.region.Chunk(cx, cz)
- switch err {
- case nil:
- case mcmap.NotAvailable:
- return
- default:
- mw.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err))
- return
- }
+func (mw *MapWidget) compose() {
+ drawable := mw.pixmap.GetDrawable()
+ gc := mw.pixmapGC
- chunk.SetBiome(bx, bz, bio)
+ drawable.DrawDrawable(gc, mw.bg.GetDrawable(), 0, 0, 0, 0, -1, -1)
- var newcol *gdk.Color
- if mw.fixSnowIce {
- if coldBiome[bio] {
- newcol = fixFreeze(bx, bz, chunk)
- } else {
- newcol = fixMelt(bx, bz, chunk)
- }
+ var tiles map[XZPos]*gdk.Pixmap
+ if mw.showBiomes {
+ tiles = mw.regWrap.Biotiles
+ } else {
+ tiles = mw.regWrap.Maptiles
}
- chunk.MarkModified()
+ for pos, tile := range tiles {
+ x := (pos.X * tileSize) - mw.offX
+ y := (pos.Z * tileSize) - mw.offZ
- // Update cache
- if bc, ok := mw.bioCache[pos]; ok {
- bc[bz*mcmap.ChunkSizeXZ+bx] = bio
+ drawable.DrawDrawable(gc, tile.GetDrawable(), 0, 0, x, y, tileSize, tileSize)
}
+}
- // Update tile
- if biotile, ok := mw.biotiles[pos]; ok {
- gdk.ThreadsEnter()
-
- drawable := biotile.GetDrawable()
- gc := gdk.NewGC(drawable)
- gc.SetRgbFgColor(bioColors[bio])
- drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom)
-
- if newcol != nil {
- drawable = mw.maptiles[pos].GetDrawable()
- gc = gdk.NewGC(drawable)
- gc.SetRgbFgColor(newcol)
- drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom)
- }
+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 NewMapWidget(reportFail func(msg string), updateInfo func(x, z int, bio mcmap.Biome), setBusy func(bool)) *MapWidget {
- mw := &MapWidget{reportFail: reportFail, updateInfo: updateInfo, setBusy: setBusy, toolsEnabled: true}
- mw.init()
+func NewMapWidget(guicbs GUICallbacks) *MapWidget {
+ dArea := gtk.NewDrawingArea()
+ redraw := make(chan bool)
+
+ mw := &MapWidget{
+ dArea: dArea,
+ guicbs: guicbs,
+ redraw: redraw,
+ showBiomes: true,
+ regWrap: NewRegionWrapper(redraw, guicbs),
+ mx1: -1,
+ my1: -1,
+ }
+ go mw.guiUpdater()
+
+ dArea.Connect("configure-event", mw.configure)
+ dArea.Connect("expose-event", mw.expose)
+ dArea.Connect("motion-notify-event", mw.movement)
+ dArea.Connect("button-press-event", mw.buttonChanged)
+ dArea.Connect("button-release-event", mw.buttonChanged)
+ dArea.SetEvents(int(gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK))
+
return mw
}
diff --git a/region_wrapper.go b/region_wrapper.go
new file mode 100644
index 0000000..1139de5
--- /dev/null
+++ b/region_wrapper.go
@@ -0,0 +1,363 @@
+package main
+
+import (
+ "fmt"
+ "github.com/kch42/gomcmap/mcmap"
+ "github.com/mattn/go-gtk/gdk"
+)
+
+const cacheSize = 4
+
+type RegionWrapper struct {
+ region *CachedRegion
+
+ tileUpdates chan bool
+
+ Maptiles map[XZPos]*gdk.Pixmap
+ Biotiles map[XZPos]*gdk.Pixmap
+ bioCache map[XZPos][]mcmap.Biome
+
+ redraw chan<- bool
+ guicbs GUICallbacks
+
+ toolsEnabled bool
+ tool Tool
+
+ fixSnowIce bool
+
+ bio mcmap.Biome
+
+ startX, startZ, endX, endZ int
+}
+
+func renderTile(chunk *mcmap.Chunk) (maptile, biotile *gdk.Pixmap, biocache []mcmap.Biome) {
+ maptile = emptyPixmap(tileSize, tileSize, 24)
+ mtDrawable := maptile.GetDrawable()
+ mtGC := gdk.NewGC(mtDrawable)
+
+ biotile = emptyPixmap(tileSize, tileSize, 24)
+ btDrawable := biotile.GetDrawable()
+ btGC := gdk.NewGC(btDrawable)
+
+ biocache = make([]mcmap.Biome, mcmap.ChunkRectXZ)
+
+ i := 0
+ for z := 0; z < mcmap.ChunkSizeXZ; z++ {
+ scanX:
+ for x := 0; x < mcmap.ChunkSizeXZ; x++ {
+ bio := chunk.Biome(x, z)
+ btGC.SetRgbFgColor(bioColors[bio])
+ btDrawable.DrawRectangle(btGC, true, x*zoom, z*zoom, zoom, zoom)
+
+ biocache[i] = bio
+ i++
+
+ 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
+}
+
+func (rw *RegionWrapper) tileUpdater() {
+ for _ = range rw.tileUpdates {
+ todelete := make(map[XZPos]bool)
+
+ for pos := range rw.Maptiles {
+ if (pos.X < rw.startX) || (pos.Z < rw.startZ) || (pos.X >= rw.endX) || (pos.Z >= rw.endZ) {
+ todelete[pos] = true
+ }
+ }
+
+ gdk.ThreadsEnter()
+ for pos := range todelete {
+ if tile, ok := rw.Maptiles[pos]; ok {
+ tile.Unref()
+ delete(rw.Maptiles, pos)
+ }
+
+ if tile, ok := rw.Biotiles[pos]; ok {
+ tile.Unref()
+ delete(rw.Biotiles, pos)
+ }
+
+ if _, ok := rw.bioCache[pos]; ok {
+ delete(rw.bioCache, pos)
+ }
+ }
+
+ if rw.region != nil {
+ for z := rw.startZ; z < rw.endZ; z++ {
+ scanX:
+ for x := rw.startX; x < rw.endX; x++ {
+ pos := XZPos{x, z}
+
+ if _, ok := rw.Biotiles[pos]; ok {
+ continue scanX
+ }
+
+ chunk, err := rw.region.Chunk(x, z)
+ switch err {
+ case nil:
+ case mcmap.NotAvailable:
+ continue scanX
+ default:
+ rw.guicbs.reportFail(fmt.Sprintf("Could not get chunk %d, %d: %s", x, z, err))
+ return
+ }
+
+ rw.Maptiles[pos], rw.Biotiles[pos], rw.bioCache[pos] = renderTile(chunk)
+ chunk.MarkUnused()
+
+ gdk.ThreadsLeave()
+ rw.redraw <- true
+ gdk.ThreadsEnter()
+ }
+ }
+ }
+
+ gdk.ThreadsLeave()
+ }
+}
+
+func (rw *RegionWrapper) SetRegion(region *mcmap.Region) {
+ if rw.RegionLoaded() {
+ rw.flushTiles()
+ }
+ rw.region = NewCachedRegion(region, cacheSize)
+
+ rw.tileUpdates <- true
+}
+
+func (rw *RegionWrapper) SetChunkBounds(startX, startZ, endX, endZ int) {
+ rw.startX = startX
+ rw.startZ = startZ
+ rw.endX = endX
+ rw.endZ = endZ
+}
+
+func (rw *RegionWrapper) SetTool(t Tool) { rw.tool = t }
+func (rw *RegionWrapper) SetBiome(bio mcmap.Biome) { rw.bio = bio }
+func (rw *RegionWrapper) SetFixSnowIce(b bool) { rw.fixSnowIce = b }
+
+func (rw *RegionWrapper) RegionLoaded() bool { return rw.region != nil }
+func (rw *RegionWrapper) ToolSingleClick() bool { return rw.tool.SingleClick() }
+
+func (rw *RegionWrapper) flushTiles() {
+ if err := rw.region.Flush(); err != nil {
+ rw.guicbs.reportFail(fmt.Sprintf("Error while flushing cache: %s", err))
+ return
+ }
+
+ gdk.ThreadsEnter()
+ for _, mt := range rw.Maptiles {
+ mt.Unref()
+ }
+ for _, bt := range rw.Biotiles {
+ bt.Unref()
+ }
+ gdk.ThreadsLeave()
+
+ rw.Maptiles = make(map[XZPos]*gdk.Pixmap)
+ rw.Biotiles = make(map[XZPos]*gdk.Pixmap)
+ rw.bioCache = make(map[XZPos][]mcmap.Biome)
+}
+
+func (rw *RegionWrapper) Save() {
+ gdk.ThreadsLeave()
+ rw.flushTiles()
+ gdk.ThreadsEnter()
+
+ if err := rw.region.Flush(); err != nil {
+ rw.guicbs.reportFail(fmt.Sprintf("Error while flushing cache: %s", err))
+ return
+ }
+ if err := rw.region.Region.Save(); err != nil {
+ rw.guicbs.reportFail(fmt.Sprintf("Error while saving: %s", err))
+ return
+ }
+}
+
+func (rw *RegionWrapper) UseTool(x, z int) {
+ gdk.ThreadsLeave()
+ defer gdk.ThreadsEnter()
+
+ if !rw.toolsEnabled {
+ return
+ }
+
+ if rw.tool.IsSlow() {
+ rw.toolsEnabled = false
+ rw.guicbs.setBusy(true)
+
+ go func() {
+ rw.tool.Do(rw.bio, rw, x, z)
+ rw.guicbs.setBusy(false)
+ rw.toolsEnabled = true
+
+ rw.redraw <- true
+ }()
+ } else {
+ rw.tool.Do(rw.bio, rw, x, z)
+ }
+}
+
+func (rw *RegionWrapper) GetBiomeAt(x, z int) (mcmap.Biome, bool) {
+ cx, cz, bx, bz := mcmap.BlockToChunk(x, z)
+ pos := XZPos{cx, cz}
+
+ if bc, ok := rw.bioCache[pos]; ok {
+ return bc[bz*mcmap.ChunkSizeXZ+bx], true
+ }
+
+ if !rw.RegionLoaded() {
+ return mcmap.BioUncalculated, false
+ }
+
+ chunk, err := rw.region.Chunk(cx, cz)
+ switch err {
+ case nil:
+ case mcmap.NotAvailable:
+ return mcmap.BioUncalculated, false
+ default:
+ rw.guicbs.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err))
+ return mcmap.BioUncalculated, false
+ }
+
+ bc := make([]mcmap.Biome, mcmap.ChunkRectXZ)
+ i := 0
+ for z := 0; z < mcmap.ChunkSizeXZ; z++ {
+ for x := 0; x < mcmap.ChunkSizeXZ; x++ {
+ bc[i] = chunk.Biome(x, z)
+ i++
+ }
+ }
+ rw.bioCache[pos] = bc
+
+ return chunk.Biome(bx, bz), true
+}
+
+func fixFreeze(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) {
+ for y := chunk.Height(bx, bz); y >= 0; y-- {
+ if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir {
+ if (blk.ID == mcmap.BlkStationaryWater) || (blk.ID == mcmap.BlkWater) {
+ blk.ID = mcmap.BlkIce
+ newcol = blockColors[mcmap.BlkIce]
+ } else if blockCanSnowIn[blk.ID] {
+ if yFix := y + 1; yFix < mcmap.ChunkSizeY {
+ blkFix := chunk.Block(bx, yFix, bz)
+ blkFix.ID = mcmap.BlkSnow
+ blkFix.Data = 0x0
+ newcol = blockColors[mcmap.BlkSnow]
+ }
+ }
+
+ break
+ }
+ }
+
+ return
+}
+
+func fixMelt(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) {
+ for y := chunk.Height(bx, bz); y >= 0; y-- {
+ if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir {
+ if blk.ID == mcmap.BlkIce {
+ blk.ID = mcmap.BlkStationaryWater
+ blk.Data = 0x0
+ newcol = blockColors[mcmap.BlkStationaryWater]
+ } else if blk.ID == mcmap.BlkSnow {
+ blk.ID = mcmap.BlkAir
+ for y2 := y - 1; y2 >= 0; y2-- {
+ if col, ok := blockColors[chunk.Block(bx, y2, bz).ID]; ok {
+ newcol = col
+ break
+ }
+ }
+ }
+
+ break
+ }
+ }
+
+ return
+}
+
+func (rw *RegionWrapper) SetBiomeAt(x, z int, bio mcmap.Biome) {
+ cx, cz, bx, bz := mcmap.BlockToChunk(x, z)
+ pos := XZPos{cx, cz}
+
+ chunk, err := rw.region.Chunk(cx, cz)
+ switch err {
+ case nil:
+ case mcmap.NotAvailable:
+ return
+ default:
+ rw.guicbs.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err))
+ return
+ }
+
+ chunk.SetBiome(bx, bz, bio)
+
+ var newcol *gdk.Color
+ if rw.fixSnowIce {
+ if coldBiome[bio] {
+ newcol = fixFreeze(bx, bz, chunk)
+ } else {
+ newcol = fixMelt(bx, bz, chunk)
+ }
+ }
+
+ chunk.MarkModified()
+
+ // Update cache
+ if bc, ok := rw.bioCache[pos]; ok {
+ bc[bz*mcmap.ChunkSizeXZ+bx] = bio
+ }
+
+ // Update tile
+ if biotile, ok := rw.Biotiles[pos]; ok {
+ gdk.ThreadsEnter()
+
+ drawable := biotile.GetDrawable()
+ gc := gdk.NewGC(drawable)
+ gc.SetRgbFgColor(bioColors[bio])
+ drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom)
+
+ if newcol != nil {
+ drawable = rw.Maptiles[pos].GetDrawable()
+ gc = gdk.NewGC(drawable)
+ gc.SetRgbFgColor(newcol)
+ drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom)
+ }
+
+ gdk.ThreadsLeave()
+ }
+}
+
+func (rw *RegionWrapper) UpdateTiles() {
+ rw.tileUpdates <- true
+}
+
+func NewRegionWrapper(redraw chan<- bool, guicbs GUICallbacks) *RegionWrapper {
+ rw := &RegionWrapper{
+ redraw: redraw,
+ tileUpdates: make(chan bool),
+ toolsEnabled: true,
+ guicbs: guicbs,
+ Maptiles: make(map[XZPos]*gdk.Pixmap),
+ Biotiles: make(map[XZPos]*gdk.Pixmap),
+ bioCache: make(map[XZPos][]mcmap.Biome),
+ }
+ go rw.tileUpdater()
+ return rw
+}