From e338efa5d5708d8797fc1bfd30c163415d48f3c7 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Thu, 15 Aug 2013 20:56:19 +0200 Subject: Added MapWidget It already draws the map and the biomes and can be moved with the middle mouse button. --- blockcolors.go | 144 ++++++++++++++++++++++++++ main.go | 29 +++++- mapwidget.go | 321 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 491 insertions(+), 3 deletions(-) create mode 100644 blockcolors.go create mode 100644 mapwidget.go diff --git a/blockcolors.go b/blockcolors.go new file mode 100644 index 0000000..bac126b --- /dev/null +++ b/blockcolors.go @@ -0,0 +1,144 @@ +package main + +import ( + "github.com/kch42/gomcmap/mcmap" + "github.com/mattn/go-gtk/gdk" +) + +var blockColors = map[mcmap.BlockID]*gdk.Color{ + mcmap.BlkStone: gdk.NewColor("#666666"), + mcmap.BlkGrassBlock: gdk.NewColor("#00aa00"), + mcmap.BlkDirt: gdk.NewColor("#644804"), + mcmap.BlkCobblestone: gdk.NewColor("#7a7a7a"), + mcmap.BlkWoodPlanks: gdk.NewColor("#a4721c"), + mcmap.BlkBedrock: gdk.NewColor("#111111"), + mcmap.BlkWater: gdk.NewColor("#0000ff"), + mcmap.BlkStationaryWater: gdk.NewColor("#0000ff"), + mcmap.BlkLava: gdk.NewColor("#ff4400"), + mcmap.BlkStationaryLava: gdk.NewColor("#ff4400"), + mcmap.BlkSand: gdk.NewColor("#f1ee85"), + mcmap.BlkGravel: gdk.NewColor("#9ba3a9"), + mcmap.BlkGoldOre: gdk.NewColor("#ffa200"), + mcmap.BlkIronOre: gdk.NewColor("#e1e1e1"), + mcmap.BlkCoalOre: gdk.NewColor("#333333"), + mcmap.BlkWood: gdk.NewColor("#a4721c"), + mcmap.BlkLeaves: gdk.NewColor("#57a100"), + mcmap.BlkGlass: gdk.NewColor("#eeeeff"), + mcmap.BlkLapisLazuliOre: gdk.NewColor("#3114e3"), + mcmap.BlkLapisLazuliBlock: gdk.NewColor("#3114e3"), + mcmap.BlkDispenser: gdk.NewColor("#7a7a7a"), + mcmap.BlkSandstone: gdk.NewColor("#f1ee85"), + mcmap.BlkNoteBlock: gdk.NewColor("#a4721c"), + mcmap.BlkBed: gdk.NewColor("#a00000"), + mcmap.BlkPoweredRail: gdk.NewColor("#ff0000"), + mcmap.BlkDetectorRail: gdk.NewColor("#ff0000"), + mcmap.BlkStickyPiston: gdk.NewColor("#91ba12"), + mcmap.BlkCobweb: gdk.NewColor("#dddddd"), + mcmap.BlkGrass: gdk.NewColor("#a0f618"), + mcmap.BlkPiston: gdk.NewColor("#a4721c"), + mcmap.BlkPistonExtension: gdk.NewColor("#a4721c"), + mcmap.BlkWool: gdk.NewColor("#ffffff"), + mcmap.BlkBlockOfGold: gdk.NewColor("#ffa200"), + mcmap.BlkBlockOfIron: gdk.NewColor("#e1e1e1"), + mcmap.BlkTNT: gdk.NewColor("#a20022"), + mcmap.BlkBookshelf: gdk.NewColor("#a4721c"), + mcmap.BlkMossStone: gdk.NewColor("#589b71"), + mcmap.BlkObsidian: gdk.NewColor("#111144"), + mcmap.BlkTorch: gdk.NewColor("#ffcc00"), + mcmap.BlkFire: gdk.NewColor("#ffcc00"), + mcmap.BlkMonsterSpawner: gdk.NewColor("#344e6a"), + mcmap.BlkOakWoodStairs: gdk.NewColor("#a4721c"), + mcmap.BlkChest: gdk.NewColor("#a4721c"), + mcmap.BlkRedstoneWire: gdk.NewColor("#ff0000"), + mcmap.BlkDiamondOre: gdk.NewColor("#00fff6"), + mcmap.BlkBlockOfDiamond: gdk.NewColor("#00fff6"), + mcmap.BlkCraftingTable: gdk.NewColor("#a4721c"), + mcmap.BlkWheat: gdk.NewColor("#e7ae00"), + mcmap.BlkFarmland: gdk.NewColor("#644804"), + mcmap.BlkFurnace: gdk.NewColor("#7a7a7a"), + mcmap.BlkBurningFurnace: gdk.NewColor("#7a7a7a"), + mcmap.BlkSignPost: gdk.NewColor("#a4721c"), + mcmap.BlkWoodenDoor: gdk.NewColor("#a4721c"), + mcmap.BlkLadders: gdk.NewColor("#a4721c"), + mcmap.BlkRail: gdk.NewColor("#dbdbdb"), + mcmap.BlkCobblestoneStairs: gdk.NewColor("#7a7a7a"), + mcmap.BlkWallSign: gdk.NewColor("#a4721c"), + mcmap.BlkLever: gdk.NewColor("#a4721c"), + mcmap.BlkStonePressurePlate: gdk.NewColor("#666666"), + mcmap.BlkIronDoor: gdk.NewColor("#e1e1e1"), + mcmap.BlkWoodenPressurePlate: gdk.NewColor("#a4721c"), + mcmap.BlkRedstoneOre: gdk.NewColor("#a00000"), + mcmap.BlkGlowingRedstoneOre: gdk.NewColor("#ff0000"), + mcmap.BlkRedstoneTorchInactive: gdk.NewColor("#ff0000"), + mcmap.BlkRedstoneTorchActive: gdk.NewColor("#ff0000"), + mcmap.BlkStoneButton: gdk.NewColor("#666666"), + mcmap.BlkSnow: gdk.NewColor("#e5fffe"), + mcmap.BlkIce: gdk.NewColor("#9fdcff"), + mcmap.BlkSnowBlock: gdk.NewColor("#e5fffe"), + mcmap.BlkCactus: gdk.NewColor("#01bc3a"), + mcmap.BlkClay: gdk.NewColor("#767a82"), + mcmap.BlkSugarCane: gdk.NewColor("#12db50"), + mcmap.BlkJukebox: gdk.NewColor("#a4721c"), + mcmap.BlkFence: gdk.NewColor("#a4721c"), + mcmap.BlkPumpkin: gdk.NewColor("#ff7000"), + mcmap.BlkNetherrack: gdk.NewColor("#851c2d"), + mcmap.BlkSoulSand: gdk.NewColor("#796a59"), + mcmap.BlkGlowstone: gdk.NewColor("#ffff00"), + mcmap.BlkNetherPortal: gdk.NewColor("#ff00ff"), + mcmap.BlkJackOLantern: gdk.NewColor("#ff7000"), + mcmap.BlkRedstoneRepeaterInactive: gdk.NewColor("#ff0000"), + mcmap.BlkRedstoneRepeaterActive: gdk.NewColor("#ff0000"), + mcmap.BlkTrapdoor: gdk.NewColor("#a4721c"), + mcmap.BlkStoneBricks: gdk.NewColor("#666666"), + mcmap.BlkHugeBrownMushroom: gdk.NewColor("#b07859"), + mcmap.BlkHugeRedMushroom: gdk.NewColor("#dd0000"), + mcmap.BlkIronBars: gdk.NewColor("#e1e1e1"), + mcmap.BlkGlassPane: gdk.NewColor("#eeeeff"), + mcmap.BlkMelon: gdk.NewColor("#9ac615"), + mcmap.BlkVines: gdk.NewColor("#50720d"), + mcmap.BlkFenceGate: gdk.NewColor("#a4721c"), + mcmap.BlkBrickStairs: gdk.NewColor("#c42500"), + mcmap.BlkStoneBrickStairs: gdk.NewColor("#666666"), + mcmap.BlkMycelium: gdk.NewColor("#7c668c"), + mcmap.BlkLilyPad: gdk.NewColor("#50720d"), + mcmap.BlkNetherBrick: gdk.NewColor("#c42500"), + mcmap.BlkNetherBrickFence: gdk.NewColor("#c42500"), + mcmap.BlkNetherBrickStairs: gdk.NewColor("#c42500"), + mcmap.BlkEnchantmentTable: gdk.NewColor("#222244"), + mcmap.BlkBrewingStand: gdk.NewColor("#666666"), + mcmap.BlkCauldron: gdk.NewColor("#666666"), + mcmap.BlkEndPortal: gdk.NewColor("#000000"), + mcmap.BlkEndPortalBlock: gdk.NewColor("#e0dbce"), + mcmap.BlkEndStone: gdk.NewColor("#e0dbce"), + mcmap.BlkRedstoneLampInactive: gdk.NewColor("#ffff00"), + mcmap.BlkRedstoneLampActive: gdk.NewColor("#ffff00"), + mcmap.BlkSandstoneStairs: gdk.NewColor("#f1ee85"), + mcmap.BlkEmeraldOre: gdk.NewColor("#00c140"), + mcmap.BlkEnderChest: gdk.NewColor("#222244"), + mcmap.BlkBlockOfEmerald: gdk.NewColor("#00c140"), + mcmap.BlkSpruceWoodStairs: gdk.NewColor("#a4721c"), + mcmap.BlkBirchWoodStairs: gdk.NewColor("#a4721c"), + mcmap.BlkJungleWoodStairs: gdk.NewColor("#a4721c"), + mcmap.BlkCommandBlock: gdk.NewColor("#e8ec78"), + mcmap.BlkBeacon: gdk.NewColor("#00fff6"), + mcmap.BlkCobblestoneWall: gdk.NewColor("#7a7a7a"), + mcmap.BlkCarrots: gdk.NewColor("#ff6000"), + mcmap.BlkPotatoes: gdk.NewColor("#c6cd0c"), + mcmap.BlkWoodenButton: gdk.NewColor("#a4721c"), + mcmap.BlkAnvil: gdk.NewColor("#444444"), + mcmap.BlkTrappedChest: gdk.NewColor("#a4721c"), + mcmap.BlkRedstoneComparatorInactive: gdk.NewColor("#ff0000"), + mcmap.BlkRedstoneComparatorActive: gdk.NewColor("#ff0000"), + mcmap.BlkBlockOfRedstone: gdk.NewColor("#ff0000"), + mcmap.BlkNetherQuartzOre: gdk.NewColor("#e7e7e7"), + mcmap.BlkHopper: gdk.NewColor("#444444"), + mcmap.BlkBlockOfQuartz: gdk.NewColor("#e7e7e7"), + mcmap.BlkQuartzStairs: gdk.NewColor("#e7e7e7"), + mcmap.BlkActivatorRail: gdk.NewColor("#ff0000"), + mcmap.BlkDropper: gdk.NewColor("#444444"), + mcmap.BlkStainedClay: gdk.NewColor("#767a82"), + mcmap.BlkHayBlock: gdk.NewColor("#e7ae00"), + mcmap.BlkCarpet: gdk.NewColor("#ffffff"), + mcmap.BlkHardenedClay: gdk.NewColor("#767a82"), + mcmap.BlkBlockOfCoal: gdk.NewColor("#333333"), +} diff --git a/main.go b/main.go index 9d432db..4ea998d 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,10 @@ 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" + "os" ) type GUI struct { @@ -12,6 +14,8 @@ type GUI struct { statusbar *gtk.Statusbar showbiomes *gtk.CheckButton + mapw *MapWidget + tool Tool } @@ -24,7 +28,14 @@ func (g *GUI) openWorldDlg() { } func (g *GUI) openWorld(path string) { - fmt.Println(path) + region, err := mcmap.OpenRegion(path, false) + if err != nil { + dlg := gtk.NewMessageDialog(g.window, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, "Could not load world %s:\n%s", path, err.Error()) + dlg.Run() + dlg.Destroy() + } + + go g.mapw.setRegion(region) } func (g *GUI) aboutDlg() { @@ -185,7 +196,8 @@ func (g *GUI) Init() { hbox := gtk.NewHBox(false, 0) - // TODO: Drawing area thing missing... + g.mapw = NewMapWidget(g.reportError) + hbox.PackStart(g.mapw.DArea(), true, true, 3) toolbox := g.mkToolbox() hbox.PackEnd(toolbox, false, false, 3) @@ -201,6 +213,13 @@ func (g *GUI) Init() { g.window.Connect("destroy", g.exitApp) } +func (g *GUI) reportError(msg string) { + dlg := gtk.NewMessageDialog(g.window, gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg) + dlg.Run() + dlg.Destroy() + os.Exit(1) +} + func (g *GUI) mkUpdateToolFx(rb *gtk.RadioButton, t Tool) func() { return func() { if rb.GetActive() { @@ -226,7 +245,7 @@ func (g *GUI) setBiome(bio mcmap.Biome) { } func (g *GUI) showbiomesToggled() { - fmt.Printf("Show biomes: %v\n", g.showbiomes.GetActive()) + g.mapw.SetShowBiomes(g.showbiomes.GetActive()) } func (g *GUI) undo() { @@ -242,6 +261,9 @@ func (g *GUI) exitApp() { } func main() { + glib.ThreadInit(nil) + gdk.ThreadsInit() + gdk.ThreadsEnter() gtk.Init(nil) gui := new(GUI) @@ -249,4 +271,5 @@ func main() { gui.Show() gtk.Main() + gdk.ThreadsLeave() } 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 +} -- cgit v1.2.3-54-g00ecf