diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25fc6344..5d9de1ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: - name: Set up Go environment uses: actions/setup-go@v2.1.3 with: - go-version: 1.16 + go-version: 1.16.5 id: go - name: Cache Go modules diff --git a/Makefile b/Makefile index 55958a50..ae31a0c8 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ setup: @git submodule update --init --recursive @$(GOCMD) get -v -t -d ./... @$(GOCMD) mod download -x + @$(GOCMD) get golang.org/x/tools/cmd/stringer @$(GOCMD) install golang.org/x/tools/cmd/stringer @$(GOCMD) generate -v ./... diff --git a/README.md b/README.md index 48641b68..8acb697d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ ![Logo](hsassets/images/d2logo.png) [Join us on Discord!](https://discord.gg/pRy8tdc)\ +[Join us on IRC!](ircs://irc.libera.chat/#opendiablo2)\ [Development Live stream](https://www.twitch.tv/essial/)\ [Support us on Patreon](https://www.patreon.com/bePatron?u=37261055) diff --git a/abysswrapper/abysswrapper.go b/abysswrapper/abysswrapper.go index 31bf0799..5fba5893 100644 --- a/abysswrapper/abysswrapper.go +++ b/abysswrapper/abysswrapper.go @@ -48,7 +48,7 @@ func (a *AbyssWrapper) Write(p []byte) (n int, err error) { return n, nil } -// Launch launchs abyss wrapper +// Launch launches abyss wrapper func (a *AbyssWrapper) Launch(config *hsconfig.Config, output io.Writer) error { a.mutex.RLock() if a.running { diff --git a/hsassets/assets.go b/hsassets/assets.go index 86254a28..d95698fa 100644 --- a/hsassets/assets.go +++ b/hsassets/assets.go @@ -4,7 +4,7 @@ import ( _ "embed" // this is standard solution for embed ) -// these variables are links to existing icones used in project +// these variables are links to existing icons used in project // nolint:gochecknoglobals // go:embed directive works only for globals // https://github.com/golangci/golangci-lint/issues/1727 var ( diff --git a/hscommon/hsfiletypes/filetype.go b/hscommon/hsfiletypes/filetype.go index 0d329b66..d497d51f 100644 --- a/hscommon/hsfiletypes/filetype.go +++ b/hscommon/hsfiletypes/filetype.go @@ -94,9 +94,9 @@ func (f FileType) FileExtension() string { type fileTypeCheckFn = func(data *[]byte) FileType -// SubtypeCheckFn returns a function to check a file. This is important for +// subtypeCheckFn returns a function to check a file. This is important for // distinguishing between files that share a common file extension. -func (f FileType) SubtypeCheckFn() fileTypeCheckFn { +func (f FileType) subtypeCheckFn() fileTypeCheckFn { table := map[FileType]fileTypeCheckFn{ FileTypeTBL: determineSubtypeTBL, FileTypeTBLFontTable: determineSubtypeTBL, @@ -114,7 +114,7 @@ func GetFileTypeFromExtension(extension string, data *[]byte) (FileType, error) continue } - fnDetermine := fileType.SubtypeCheckFn() + fnDetermine := fileType.subtypeCheckFn() if fnDetermine == nil { return fileType, nil } diff --git a/hscommon/hsnode/node.go b/hscommon/hsnode/node.go index f4ae3eb5..3baafb39 100644 --- a/hscommon/hsnode/node.go +++ b/hscommon/hsnode/node.go @@ -1,4 +1,4 @@ -// Package hsnode contains game nodes +// Package hsnode contains an implementation of a node graph, as parent and child nodes. package hsnode import "strings" diff --git a/hscommon/hsutil/datautils.go b/hscommon/hsutil/datautils.go index 2063ce31..05b3d893 100644 --- a/hscommon/hsutil/datautils.go +++ b/hscommon/hsutil/datautils.go @@ -12,9 +12,9 @@ import ( "github.com/OpenDiablo2/dialog" ) -const miliseconds = 1000 +const milliseconds = 1000 -// BoolToInt converts bool into 32-bit intager +// BoolToInt converts bool into 32-bit integer // if b is true, then returns 1, else 0 func BoolToInt(b bool) int32 { if b { @@ -48,7 +48,7 @@ func ExportToGif(images []*image.RGBA, delay int32) error { // reload static image and construct outGif for _, img := range images { // FROM TUTORIAL: - // Read each frame GIF image with gif.Decode. If we read JPEG images, we have to convert them programatically + // Read each frame GIF image with gif.Decode. If we read JPEG images, we have to convert them programmatically // (goanigiffy does this by calling gif.Encode and gif.Decode). g := bytes.NewBuffer([]byte{}) @@ -63,11 +63,11 @@ func ExportToGif(images []*image.RGBA, delay int32) error { } outGif.Image = append(outGif.Image, inGif.(*image.Paletted)) - outGif.Delay = append(outGif.Delay, int(delay/miliseconds)) + outGif.Delay = append(outGif.Delay, int(delay/milliseconds)) } // save gif image - file, err := os.OpenFile(filepath.Clean(filePath), os.O_WRONLY|os.O_CREATE, 0o600) + file, err := os.OpenFile(filepath.Clean(filePath), os.O_WRONLY|os.O_CREATE, defaultFilePermissions) if err != nil { return fmt.Errorf("error creating a new file: %w", err) } diff --git a/hsconfig/doc.go b/hsconfig/doc.go index 2136ea06..e6e03439 100644 --- a/hsconfig/doc.go +++ b/hsconfig/doc.go @@ -1,2 +1,2 @@ -// Package hsconfig contains app configs +// Package hsconfig provides the Hellspawner application config file implementation. package hsconfig diff --git a/hswidget/animdatawidget/widget.go b/hswidget/animdatawidget/widget.go index 1f3a2621..179e8b71 100644 --- a/hswidget/animdatawidget/widget.go +++ b/hswidget/animdatawidget/widget.go @@ -73,12 +73,14 @@ func (p *widget) buildAnimationsList() { list := make([]giu.Widget, len(keys)) + const imageButtonSize = 13 + for idx, name := range keys { currentIdx := idx list[idx] = giu.Row( hswidget.MakeImageButton( "##"+p.id+"deleteEntry"+strconv.Itoa(currentIdx), - 13, 13, + imageButtonSize, imageButtonSize, state.deleteIcon, func() { p.deleteEntry(state.mapKeys[currentIdx]) diff --git a/hswidget/cofwidget/state.go b/hswidget/cofwidget/state.go index 62c2d002..8dc073b2 100644 --- a/hswidget/cofwidget/state.go +++ b/hswidget/cofwidget/state.go @@ -130,7 +130,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.selectable = (selectable == 1) + s.selectable = selectable == 1 transparent, err := sr.ReadByte() if err != nil { @@ -139,7 +139,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.transparent = (transparent == 1) + s.transparent = transparent == 1 s.drawEffect, err = sr.ReadInt32() if err != nil { diff --git a/hswidget/cofwidget/widget.go b/hswidget/cofwidget/widget.go index b7d66573..d00b2aa2 100644 --- a/hswidget/cofwidget/widget.go +++ b/hswidget/cofwidget/widget.go @@ -421,12 +421,12 @@ func (p *widget) makeAddLayerLayout() giu.Layout { drawEffectList := make([]string, d2enum.DrawEffectNone+1) for i := d2enum.DrawEffectPctTransparency25; i <= d2enum.DrawEffectNone; i++ { - drawEffectList[int(i)] = strconv.Itoa(int(i)) + " (" + i.String() + ")" + drawEffectList[i] = strconv.Itoa(int(i)) + " (" + i.String() + ")" } weaponClassList := make([]string, d2enum.WeaponClassTwoHandToHand+1) for i := d2enum.WeaponClassNone; i <= d2enum.WeaponClassTwoHandToHand; i++ { - weaponClassList[int(i)] = i.String() + " (" + i.Name() + ")" + weaponClassList[i] = i.String() + " (" + i.Name() + ")" } return giu.Layout{ diff --git a/hswidget/dc6widget/widget.go b/hswidget/dc6widget/widget.go index e281bfcf..3fde860d 100644 --- a/hswidget/dc6widget/widget.go +++ b/hswidget/dc6widget/widget.go @@ -118,7 +118,9 @@ func (p *widget) makeViewerLayout() giu.Layout { imgui.SliderInt("Frames", &viewerState.controls.frame, 0, int32(p.dc6.FramesPerDirection-1)) } - imgui.SliderInt("Scale", &viewerState.controls.scale, 1, 8) + const minScale, maxScale = 1, 8 + + imgui.SliderInt("Scale", &viewerState.controls.scale, minScale, maxScale) imgui.EndGroup() }), diff --git a/hswidget/dccwidget/doc.go b/hswidget/dccwidget/doc.go index 0c7572f8..6ff38f0f 100644 --- a/hswidget/dccwidget/doc.go +++ b/hswidget/dccwidget/doc.go @@ -1,3 +1,2 @@ -// Package dccwidget contains stuff responsible for -// viewing and editing DCC structures +// Package dccwidget contains stuff responsible for viewing and editing the DCC data structure package dccwidget diff --git a/hswidget/dccwidget/state.go b/hswidget/dccwidget/state.go index 21738b64..bbfab14d 100644 --- a/hswidget/dccwidget/state.go +++ b/hswidget/dccwidget/state.go @@ -114,7 +114,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.isPlaying = (isPlaying == 1) + s.isPlaying = isPlaying == 1 repeat, err := sr.ReadByte() if err != nil { @@ -122,7 +122,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.repeat = (repeat == 1) + s.repeat = repeat == 1 s.tickTime, err = sr.ReadInt32() if err != nil { diff --git a/hswidget/dccwidget/widget.go b/hswidget/dccwidget/widget.go index dd743e70..2ff639a8 100644 --- a/hswidget/dccwidget/widget.go +++ b/hswidget/dccwidget/widget.go @@ -105,7 +105,9 @@ func (p *widget) Build() { imgui.SliderInt("Frames", &viewerState.controls.frame, 0, int32(p.dcc.FramesPerDirection-1)) } - imgui.SliderInt("Scale", &viewerState.controls.scale, 1, 8) + const minScale, maxScale = 1, 8 + + imgui.SliderInt("Scale", &viewerState.controls.scale, minScale, maxScale) imgui.EndGroup() }), diff --git a/hswidget/doc.go b/hswidget/doc.go index de8c18e0..0088f4d6 100644 --- a/hswidget/doc.go +++ b/hswidget/doc.go @@ -1,2 +1,2 @@ -// Package hswidget contains widgets for each editor +// Package hswidget contains a generic editor widget implementation, along with with concrete editor implementations. package hswidget diff --git a/hswidget/ds1widget/doc.go b/hswidget/ds1widget/doc.go index ff1d948d..532c40ca 100644 --- a/hswidget/ds1widget/doc.go +++ b/hswidget/ds1widget/doc.go @@ -1,3 +1,2 @@ -// Package ds1widget provides a giu.Widget implementation for viewing and editing -// the DS1 files. +// Package ds1widget provides a giu.Widget for viewing and editing the DS1 data structure. package ds1widget diff --git a/hswidget/ds1widget/widget.go b/hswidget/ds1widget/widget.go index 536b88b1..aec0577c 100644 --- a/hswidget/ds1widget/widget.go +++ b/hswidget/ds1widget/widget.go @@ -333,11 +333,10 @@ func (p *widget) makeObjectLayout(state *widgetState) giu.Layout { } if len(obj.Paths) > 0 { - l = append( - l, - giu.Dummy(1, 16), - p.makePathLayout(state, obj), - ) + const spacerHeight = 16 + + vspace := giu.Dummy(1, spacerHeight) + l = append(l, vspace, p.makePathLayout(state, obj)) } return l diff --git a/hswidget/dt1widget/doc.go b/hswidget/dt1widget/doc.go index 4ac22fb7..f8e43dec 100644 --- a/hswidget/dt1widget/doc.go +++ b/hswidget/dt1widget/doc.go @@ -1,3 +1,2 @@ -// Package dt1widget contains data about dt1 widget, necessary to -// edit and view dt1 file structure +// Package dt1widget contains a giu widget implementation for viewing and editing the dt1 data structure package dt1widget diff --git a/hswidget/dt1widget/state.go b/hswidget/dt1widget/state.go index ec45394c..2e132f7b 100644 --- a/hswidget/dt1widget/state.go +++ b/hswidget/dt1widget/state.go @@ -91,7 +91,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.showGrid = (showGrid == 1) + s.showGrid = showGrid == 1 showFloor, err := sr.ReadByte() if err != nil { @@ -100,7 +100,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.showFloor = (showFloor == 1) + s.showFloor = showFloor == 1 showWall, err := sr.ReadByte() if err != nil { @@ -109,7 +109,7 @@ func (s *widgetState) Decode(data []byte) { return } - s.showWall = (showWall == 1) + s.showWall = showWall == 1 s.subtileFlag, err = sr.ReadInt32() if err != nil { diff --git a/hswidget/dt1widget/sub_tile_flags.go b/hswidget/dt1widget/sub_tile_flags.go index cf8c0dd6..df3a7e48 100644 --- a/hswidget/dt1widget/sub_tile_flags.go +++ b/hswidget/dt1widget/sub_tile_flags.go @@ -33,11 +33,11 @@ func subTileString(subtile int32) string { func getFlagFromPos(x, y int) int { var subtileLookup = [5][5]int{ - {20, 21, 22, 23, 24}, - {15, 16, 17, 18, 19}, - {10, 11, 12, 13, 14}, - {5, 6, 7, 8, 9}, {0, 1, 2, 3, 4}, + {5, 6, 7, 8, 9}, + {10, 11, 12, 13, 14}, + {15, 16, 17, 18, 19}, + {20, 21, 22, 23, 24}, } return subtileLookup[y][x] diff --git a/hswidget/dt1widget/widget.go b/hswidget/dt1widget/widget.go index 5b2d5b00..b24b6b4b 100644 --- a/hswidget/dt1widget/widget.go +++ b/hswidget/dt1widget/widget.go @@ -5,8 +5,11 @@ import ( "image" "image/color" "log" + "math" "strconv" + "golang.org/x/image/colornames" + "github.com/ianling/giu" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -22,11 +25,14 @@ const ( ) const ( + comboW = 280 gridMaxWidth = 160 gridMaxHeight = 80 gridDivisionsXY = 5 subtileHeight = gridMaxHeight / gridDivisionsXY subtileWidth = gridMaxWidth / gridDivisionsXY + halfTileW = subtileWidth >> 1 + halfTileH = subtileHeight >> 1 imageW, imageH = 32, 32 ) @@ -176,12 +182,15 @@ func (p *widget) makeTileTextures() { p.setState(state) } -func rangeByte(b byte, min, max float64) byte { - // nolint:gomnd // constant - return byte((float64(b)/255*(max-min) + min) * 255) -} - func (p *widget) makePixelBuffer(tile *d2dt1.Tile) (floorBuf, wallBuf []byte) { + const ( + rOff = iota // rg,b offsets + gOff + bOff + aOff + bpp // bytes per pixel + ) + tw, th := int(tile.Width), int(tile.Height) if th < 0 { th *= -1 @@ -200,10 +209,8 @@ func (p *widget) makePixelBuffer(tile *d2dt1.Tile) (floorBuf, wallBuf []byte) { decodeTileGfxData(tile.Blocks, &floor, &wall, tileYOffset, tile.Width) - // nolint:gomnd // constant - floorBuf = make([]byte, tw*th*4) // rgba, fake palette values - // nolint:gomnd // constant - wallBuf = make([]byte, tw*th*4) // rgba, fake palette values + floorBuf = make([]byte, tw*th*bpp) + wallBuf = make([]byte, tw*th*bpp) for idx := range floor { var r, g, b, alpha byte @@ -211,17 +218,16 @@ func (p *widget) makePixelBuffer(tile *d2dt1.Tile) (floorBuf, wallBuf []byte) { floorVal := floor[idx] wallVal := wall[idx] - // nolint:gomnd // constant - rPos, gPos, bPos, aPos := idx*4+0, idx*4+1, idx*4+2, idx*4+3 + rPos, gPos, bPos, aPos := idx*bpp+rOff, idx*bpp+gOff, idx*bpp+bOff, idx*bpp+aOff // the faux rgb color data here is just to make it look more interesting if p.palette != nil { col := p.palette[floorVal] r, g, b = col.R(), col.G(), col.B() } else { - r = rangeByte(floorVal, 128, 256) - g = 0 - b = rangeByte(rangeByte(floorVal, 0, 4), 128, 0) + r = floorVal + g = floorVal + b = floorVal } floorBuf[rPos] = r @@ -240,9 +246,9 @@ func (p *widget) makePixelBuffer(tile *d2dt1.Tile) (floorBuf, wallBuf []byte) { col := p.palette[wallVal] r, g, b = col.R(), col.G(), col.B() } else { - r = 0 - g = rangeByte(wallVal, 64, 196) - b = rangeByte(rangeByte(floorVal, 0, 4), 128, 0) + r = wallVal + g = wallVal + b = wallVal } wallBuf[rPos] = r @@ -345,7 +351,6 @@ func (p *widget) makeTileDisplay(state *widgetState, tile *d2dt1.Tile) *giu.Layo halfTileW, halfTileH := subtileWidth>>1, subtileHeight>>1 // make TL to BR lines - // nolint:dupl // could be changed for idx := 0; idx <= gridDivisionsXY; idx++ { p1 := image.Point{ X: left.X + (idx * halfTileW), @@ -357,18 +362,16 @@ func (p *widget) makeTileDisplay(state *widgetState, tile *d2dt1.Tile) *giu.Layo Y: p1.Y + (gridDivisionsXY * halfTileH), } - // nolint:gomnd // const - c := color.RGBA{R: 0, G: 255, B: 0, A: 255} + c := colornames.Green if idx == 0 || idx == gridDivisionsXY { - c.R = 255 + c = colornames.Yellowgreen } canvas.AddLine(p1, p2, c, 1) } // make TR to BL lines - // nolint:dupl // is ok for idx := 0; idx <= gridDivisionsXY; idx++ { p1 := image.Point{ X: left.X + (idx * halfTileW), @@ -380,11 +383,10 @@ func (p *widget) makeTileDisplay(state *widgetState, tile *d2dt1.Tile) *giu.Layo Y: p1.Y - (gridDivisionsXY * halfTileH), } - // nolint:gomnd // const - c := color.RGBA{R: 0, G: 255, B: 0, A: 255} + c := colornames.Green if idx == 0 || idx == gridDivisionsXY { - c.R = 255 + c = colornames.Yellowgreen } canvas.AddLine(p1, p2, c, 1) @@ -477,6 +479,12 @@ func (p *widget) makeTileInfoTab(tile *d2dt1.Tile) giu.Layout { roofHeight := int32(tile.RoofHeight) + const ( + vspaceHeight = 4 // px + ) + + spacer := giu.Dummy(1, vspaceHeight) + return giu.Layout{ giu.Row( giu.InputInt("##"+p.id+"inputWidth", &w).Size(inputIntW).OnChange(func() { @@ -488,13 +496,13 @@ func (p *widget) makeTileInfoTab(tile *d2dt1.Tile) giu.Layout { }), giu.Label("pixels"), ), - giu.Dummy(1, 4), + spacer, giu.Row( giu.Label("Direction: "), giu.InputInt("##"+p.id+"tileDirection", &tile.Direction).Size(inputIntW), ), - giu.Dummy(1, 4), + spacer, giu.Row( giu.Label("RoofHeight:"), @@ -502,33 +510,27 @@ func (p *widget) makeTileInfoTab(tile *d2dt1.Tile) giu.Layout { tile.RoofHeight = int16(roofHeight) }), ), - giu.Dummy(1, 4), + spacer, tileTypeInfo, - giu.Dummy(1, 4), + spacer, giu.Row( giu.Label("Style:"), giu.InputInt("##"+p.id+"style", &tile.Style).Size(inputIntW), ), - giu.Dummy(1, 4), + spacer, giu.Row( giu.Label("Sequence:"), giu.InputInt("##"+p.id+"sequence", &tile.Sequence).Size(inputIntW), ), - giu.Dummy(1, 4), + spacer, giu.Row( giu.Label("RarityFrameIndex:"), giu.InputInt("##"+p.id+"rarityFrameIndex", &tile.RarityFrameIndex).Size(inputIntW), ), - // giu.Row( - // giu.Label(fmt.Sprintf("SubTileFlags: %v", tile.SubTileFlags)), - // ), - // giu.Row( - // giu.Label(fmt.Sprintf("Blocks: %v", tile.Blocks)), - // ), } } @@ -579,33 +581,23 @@ func (p *widget) SetTileGroup(tileGroup int32) { } func (p *widget) makeSubtileFlags(state *widgetState, tile *d2dt1.Tile) giu.Layout { + subtileFlagList := make([]string, 0) + + const numberSubtileFlagTypes = 8 + for i := int32(0); i < numberSubtileFlagTypes; i++ { + subtileFlagList = append(subtileFlagList, subTileString(i)) + } + if tile.Height < 0 { tile.Height *= -1 } return giu.Layout{ - giu.SliderInt("Subtile Type", &state.controls.subtileFlag, 0, 7), - giu.Label(subTileString(state.controls.subtileFlag)), + giu.Combo("##"+p.id+"SubtileList", subtileFlagList[state.subtileFlag], subtileFlagList, &state.subtileFlag).Size(comboW), giu.Label("Edit:"), - giu.Custom(func() { - for y := 0; y < gridDivisionsXY; y++ { - layout := giu.Layout{} - for x := 0; x < gridDivisionsXY; x++ { - layout = append(layout, - giu.Checkbox("##"+strconv.Itoa(y*gridDivisionsXY+x), - p.getSubTileFieldToEdit(y+x*gridDivisionsXY), - ), - ) - } - - giu.Row(layout...).Build() - } - }), - giu.Dummy(0, 4), - giu.Label("Preview:"), p.makeSubTilePreview(tile, state), - giu.Dummy(gridMaxWidth, gridMaxHeight), + giu.Label("Click to Add/Remove flags"), } } @@ -617,8 +609,6 @@ func (p *widget) makeSubTilePreview(tile *d2dt1.Tile, state *widgetState) giu.La left := image.Point{X: 0 + pos.X, Y: (gridMaxHeight >> 1) + pos.Y} - halfTileW, halfTileH := subtileWidth>>1, subtileHeight>>1 - // make TL to BR lines for idx := 0; idx <= gridDivisionsXY; idx++ { p1 := image.Point{ // top-left point @@ -631,11 +621,10 @@ func (p *widget) makeSubTilePreview(tile *d2dt1.Tile, state *widgetState) giu.La Y: p1.Y + (gridDivisionsXY * halfTileH), } - // nolint:gomnd // const - c := color.RGBA{R: 0, G: 255, B: 0, A: 255} + c := colornames.Green if idx == 0 || idx == gridDivisionsXY { - c.R = 255 + c = colornames.Yellowgreen } for flagOffsetIdx := 0; flagOffsetIdx < gridDivisionsXY; flagOffsetIdx++ { @@ -646,26 +635,21 @@ func (p *widget) makeSubTilePreview(tile *d2dt1.Tile, state *widgetState) giu.La ox := (flagOffsetIdx + 1) * halfTileW oy := flagOffsetIdx * halfTileH - flagPoint := image.Point{ - X: p1.X + ox, - Y: p1.Y + oy, - } + flagPoint := image.Point{X: p1.X + ox, Y: p1.Y + oy} - // nolint:gomnd // const - col := color.RGBA{ - R: 0, - G: 255, - B: 255, - A: 255, - } + col := colornames.Yellow - // nolint:gomnd // constant - flag := tile.SubTileFlags[getFlagFromPos(flagOffsetIdx, 4-idx)].Encode() + subtileIdx := getFlagFromPos(flagOffsetIdx, idx%gridDivisionsXY) + flag := tile.SubTileFlags[subtileIdx].Encode() hasFlag := (flag & (1 << state.controls.subtileFlag)) > 0 + p.handleSubtileHoverAndClick(subtileIdx, flagPoint, canvas) + if hasFlag { - canvas.AddCircle(flagPoint, 3, col, 1) + const circleRadius = 3 // px + + canvas.AddCircle(flagPoint, circleRadius, col, 1) } } @@ -673,7 +657,6 @@ func (p *widget) makeSubTilePreview(tile *d2dt1.Tile, state *widgetState) giu.La } // make TR to BL lines - // nolint:dupl // also ok for idx := 0; idx <= gridDivisionsXY; idx++ { p1 := image.Point{ // bottom left point X: left.X + (idx * halfTileW), @@ -685,11 +668,10 @@ func (p *widget) makeSubTilePreview(tile *d2dt1.Tile, state *widgetState) giu.La Y: p1.Y - (gridDivisionsXY * halfTileH), } - // nolint:gomnd // const - c := color.RGBA{R: 0, G: 255, B: 0, A: 255} + c := colornames.Green if idx == 0 || idx == gridDivisionsXY { - c.R = 255 + c = colornames.Yellowgreen } canvas.AddLine(p1, p2, c, 1) @@ -697,3 +679,27 @@ func (p *widget) makeSubTilePreview(tile *d2dt1.Tile, state *widgetState) giu.La }), } } + +func (p *widget) handleSubtileHoverAndClick(subtileIdx int, flagPoint image.Point, canvas *giu.Canvas) { + mousePos := giu.GetMousePos() + delta := mousePos.Sub(flagPoint) + dx, dy := int(math.Abs(float64(delta.X))), int(math.Abs(float64(delta.Y))) + closeEnough := (dx < halfTileH) && (dy < halfTileH) + + // draw a crosshair on the point if hovered + if closeEnough { + highlight := color.RGBA{255, 255, 255, 64} + + p1, p2 := flagPoint.Sub(image.Point{X: -halfTileW}), flagPoint.Sub(image.Point{X: halfTileW}) + canvas.AddLine(p1, p2, highlight, 1) + + p3, p4 := flagPoint.Sub(image.Point{Y: -halfTileH}), flagPoint.Sub(image.Point{Y: halfTileH}) + canvas.AddLine(p3, p4, highlight, 1) + } + + // on mouse release, toggle the flag + if closeEnough && giu.IsMouseReleased(giu.MouseButtonLeft) { + bit := p.getSubTileFieldToEdit(subtileIdx) + *bit = !(*bit) + } +} diff --git a/hswidget/fonttablewidget/doc.go b/hswidget/fonttablewidget/doc.go index 28d57604..c060bc4a 100644 --- a/hswidget/fonttablewidget/doc.go +++ b/hswidget/fonttablewidget/doc.go @@ -1,3 +1,3 @@ -// Package fonttablewidget contains stuff responsible for -// font table editor widget +// Package fonttablewidget contains a giu widget implementation for viewing and editing the +// font table (tbl) data structure. package fonttablewidget diff --git a/hswidget/palettegrideditorwidget/doc.go b/hswidget/palettegrideditorwidget/doc.go index 4083faf6..5a5feff4 100644 --- a/hswidget/palettegrideditorwidget/doc.go +++ b/hswidget/palettegrideditorwidget/doc.go @@ -1,4 +1,3 @@ -// Package palettegrideditorwidget provides data for editin palette grid -// it uses palettegridwidget to display palette. -// when clicked on color, editor is started +// Package palettegrideditorwidget provides a giu widget implementation of a palette editor, for viewing and editing +// a palette. package palettegrideditorwidget diff --git a/hswidget/palettegrideditorwidget/helpers.go b/hswidget/palettegrideditorwidget/helpers.go index c17db0d6..e87da40a 100644 --- a/hswidget/palettegrideditorwidget/helpers.go +++ b/hswidget/palettegrideditorwidget/helpers.go @@ -24,17 +24,19 @@ func (p *PaletteGridEditorWidget) changeColor(state *widgetState) { // Hex2RGB converts haxadecimal color into r, g, b func Hex2RGB(hex string) (r, g, b uint8, err error) { - values, err := strconv.ParseUint(hex, 16, 32) - if err != nil { - return 0, 0, 0, fmt.Errorf("error parsing uint: %w", err) - } - const ( + base = 16 + bitSize = 32 mask = 0xFF rOffset = 16 gOffset = 8 ) + values, err := strconv.ParseUint(hex, base, bitSize) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing uint: %w", err) + } + r = uint8(values >> rOffset) g = uint8((values >> gOffset) & mask) b = uint8(values & mask) @@ -43,7 +45,9 @@ func Hex2RGB(hex string) (r, g, b uint8, err error) { } func t2x(t int64) string { - result := strconv.FormatInt(t, 16) + const base = 16 + + result := strconv.FormatInt(t, base) if len(result) == 1 { result = "0" + result } diff --git a/hswidget/palettegrideditorwidget/widget.go b/hswidget/palettegrideditorwidget/widget.go index bc15a000..f89ce2db 100644 --- a/hswidget/palettegrideditorwidget/widget.go +++ b/hswidget/palettegrideditorwidget/widget.go @@ -2,6 +2,7 @@ package palettegrideditorwidget import ( "log" + "math" "github.com/ianling/giu" @@ -137,7 +138,7 @@ func (p *PaletteGridEditorWidget) makeRGBField(id, label string, field *uint8, g }, ), ), - giu.SliderInt(id+"Slider", &f32, 0, 255).OnChange(func() { + giu.SliderInt(id+"Slider", &f32, 0, math.MaxUint8).OnChange(func() { p.changeColor(state) grid.UpdateColorTexture(state.idx) if p.onChange != nil { diff --git a/hswidget/palettegridwidget/helpers.go b/hswidget/palettegridwidget/helpers.go index b2ece144..79163d86 100644 --- a/hswidget/palettegridwidget/helpers.go +++ b/hswidget/palettegridwidget/helpers.go @@ -7,17 +7,19 @@ import ( // Hex2RGB converts haxadecimal color into r, g, b func Hex2RGB(hex string) (r, g, b uint8, err error) { - values, err := strconv.ParseUint(hex, 16, 32) - if err != nil { - return 0, 0, 0, fmt.Errorf("error parsing uint: %w", err) - } - const ( + base = 16 + bitSize = 32 mask = 0xFF rOffset = 16 gOffset = 8 ) + values, err := strconv.ParseUint(hex, base, bitSize) + if err != nil { + return 0, 0, 0, fmt.Errorf("error parsing uint: %w", err) + } + r = uint8(values >> rOffset) g = uint8((values >> gOffset) & mask) b = uint8(values & mask) @@ -26,7 +28,9 @@ func Hex2RGB(hex string) (r, g, b uint8, err error) { } func t2x(t int64) string { - result := strconv.FormatInt(t, 16) + const base = 16 + result := strconv.FormatInt(t, base) + if len(result) == 1 { result = "0" + result } diff --git a/hswidget/palettemapwidget/doc.go b/hswidget/palettemapwidget/doc.go index d806fbe5..1942bc05 100644 --- a/hswidget/palettemapwidget/doc.go +++ b/hswidget/palettemapwidget/doc.go @@ -1,2 +1,3 @@ -// Package palettemapwidget provides PL2 eidtor +// Package palettemapwidget provides a giu widget implementation of an editor for the +// PL2 palette transform data structure. package palettemapwidget diff --git a/hswidget/palettemapwidget/widget.go b/hswidget/palettemapwidget/widget.go index 42cb7126..5d3117cb 100644 --- a/hswidget/palettemapwidget/widget.go +++ b/hswidget/palettemapwidget/widget.go @@ -15,9 +15,10 @@ import ( ) const ( - comboW = 280 - layoutW, layoutH = 475, 300 - actionButtonW = layoutW + comboW = 280 + layoutW, layoutH = 475, 300 + actionButtonW = layoutW + numColorsInPalette = 256 ) type widget struct { @@ -55,7 +56,7 @@ func (p *widget) buildViewer(state *widgetState) { log.Print(err) } - baseColors := make([]palettegridwidget.PaletteColor, 256) + baseColors := make([]palettegridwidget.PaletteColor, numColorsInPalette) for n := range baseColors { baseColors[n] = palettegridwidget.PaletteColor(&p.pl2.BasePalette.Colors[n]) diff --git a/hswidget/select_palette_widget.go b/hswidget/select_palette_widget.go index 42274b37..368b63cc 100644 --- a/hswidget/select_palette_widget.go +++ b/hswidget/select_palette_widget.go @@ -41,6 +41,7 @@ func NewSelectPaletteWidget( closeCB func(), ) *SelectPaletteWidget { result := &SelectPaletteWidget{ + id: id, saveCB: saveCB, closeCB: closeCB, } diff --git a/hswindow/hsdialog/hsaboutdialog/aboutdialog.go b/hswindow/hsdialog/hsaboutdialog/aboutdialog.go index d3f5e56e..f7b83b97 100644 --- a/hswindow/hsdialog/hsaboutdialog/aboutdialog.go +++ b/hswindow/hsdialog/hsaboutdialog/aboutdialog.go @@ -1,4 +1,4 @@ -// Package hsaboutdialog contains about dialog's data +// Package hsaboutdialog provides the "About" window implementation, which shows information about hellspawner. package hsaboutdialog import ( @@ -80,7 +80,8 @@ func Create(textureLoader hscommon.TextureLoader, regularFont, titleFont, fixedF } // set string's max length - text = strings.Join(hsutil.SplitIntoLinesWithMaxWidth(text, 70), "\n") + const maxColumns = 70 + text = strings.Join(hsutil.SplitIntoLinesWithMaxWidth(text, maxColumns), "\n") result.readme = text return result, nil diff --git a/hswindow/hsdialog/hsprojectpropertiesdialog/projectpropertiesdialog.go b/hswindow/hsdialog/hsprojectpropertiesdialog/projectpropertiesdialog.go index e8632096..ff5dc2b7 100644 --- a/hswindow/hsdialog/hsprojectpropertiesdialog/projectpropertiesdialog.go +++ b/hswindow/hsdialog/hsprojectpropertiesdialog/projectpropertiesdialog.go @@ -114,6 +114,9 @@ func (p *ProjectPropertiesDialog) Build() { return false } + + const listItemHeight = 20 + // list of `Selectable widgets`; for i, mpq := range p.auxMPQNames { i := i @@ -132,7 +135,7 @@ func (p *ProjectPropertiesDialog) Build() { }), g.Selectable(mpq+"##"+"ProjectPropertiesSelectAuxMPQDialogIdx"+strconv.Itoa(i)). Selected(isSelected). - Size(mainWindowW, 20).OnClick(func() { + Size(mainWindowW, listItemHeight).OnClick(func() { if isSelected { removeMPQ(i) } else { @@ -234,8 +237,10 @@ func (p *ProjectPropertiesDialog) Build() { ), g.Row( g.Custom(func() { + const halfOpacity = 0.5 + if !canSave { - imgui.PushStyleVarFloat(imgui.StyleVarAlpha, 0.5) + imgui.PushStyleVarFloat(imgui.StyleVarAlpha, halfOpacity) } }), g.Button("Save##ProjectPropertiesDialogSave").OnClick(p.onSaveClicked), diff --git a/hswindow/hseditor/doc.go b/hswindow/hseditor/doc.go index a0641c9b..c22e2b63 100644 --- a/hswindow/hseditor/doc.go +++ b/hswindow/hseditor/doc.go @@ -1,2 +1,3 @@ -// Package hseditor contains editor's data +// Package hseditor provides editors for the various file types. These generally take the form of +// a window which displays a single widget, as well as some context-sensitive menu for the main menu bar. package hseditor diff --git a/hswindow/hseditor/editor.go b/hswindow/hseditor/editor.go index 1c26d0fd..3fc40088 100644 --- a/hswindow/hseditor/editor.go +++ b/hswindow/hseditor/editor.go @@ -64,29 +64,25 @@ func (e *Editor) Save(editor Saveable) { return } - if _, isSaveable := editor.(Saveable); isSaveable { - saveData := editor.GenerateSaveData() - if saveData == nil { - return - } + saveData := editor.GenerateSaveData() + if saveData == nil { + return + } - existingFileData, err := e.Path.GetFileBytes() - if err != nil { - fmt.Println("failed to read file before saving: ", err) - return - } + existingFileData, err := e.Path.GetFileBytes() + if err != nil { + fmt.Println("failed to read file before saving: ", err) + return + } - if bytes.Equal(saveData, existingFileData) { - // nothing to save - return - } + if bytes.Equal(saveData, existingFileData) { + // nothing to save + return + } - err = e.Path.WriteFile(saveData) - if err != nil { - fmt.Println("failed to save file: ", err) - return - } - } else { + err = e.Path.WriteFile(saveData) + if err != nil { + fmt.Println("failed to save file: ", err) return } } @@ -98,13 +94,11 @@ func (e *Editor) HasChanges(editor Saveable) bool { return false } - if _, isSaveable := editor.(Saveable); isSaveable { - newData := editor.GenerateSaveData() - if newData != nil { - oldData, err := e.Path.GetFileBytes() - if err == nil { - return !bytes.Equal(oldData, newData) - } + newData := editor.GenerateSaveData() + if newData != nil { + oldData, err := e.Path.GetFileBytes() + if err == nil { + return !bytes.Equal(oldData, newData) } } diff --git a/hswindow/hseditor/hscofeditor/cof_editor.go b/hswindow/hseditor/hscofeditor/cof_editor.go index ad186f81..2f025b10 100644 --- a/hswindow/hseditor/hscofeditor/cof_editor.go +++ b/hswindow/hseditor/hscofeditor/cof_editor.go @@ -31,7 +31,7 @@ type COFEditor struct { } // Create creates a new cof editor -func Create(config *hsconfig.Config, +func Create(_ *hsconfig.Config, tl hscommon.TextureLoader, pathEntry *hscommon.PathEntry, state []byte, diff --git a/hswindow/hseditor/hspaletteeditor/palette_editor.go b/hswindow/hseditor/hspaletteeditor/palette_editor.go index 395b2082..32577497 100644 --- a/hswindow/hseditor/hspaletteeditor/palette_editor.go +++ b/hswindow/hseditor/hspaletteeditor/palette_editor.go @@ -55,7 +55,9 @@ func Create( // Build builds a palette editor func (e *PaletteEditor) Build() { - col := make([]palettegridwidget.PaletteColor, 256) + const colorsPerPalette = 256 + + col := make([]palettegridwidget.PaletteColor, colorsPerPalette) for n, i := range e.palette.GetColors() { col[n] = palettegridwidget.PaletteColor(i) } diff --git a/hswindow/hseditor/hssoundeditor/soundeditor.go b/hswindow/hseditor/hssoundeditor/soundeditor.go index afa8d15a..73963764 100644 --- a/hswindow/hseditor/hssoundeditor/soundeditor.go +++ b/hswindow/hseditor/hssoundeditor/soundeditor.go @@ -84,6 +84,10 @@ func (s *SoundEditor) Build() { secondsCurrent := s.streamer.Position() / progressTimeModifier secondsTotal := s.streamer.Len() / progressTimeModifier + const progressBarHeight = 24 // px + + progress := float32(s.streamer.Position()) / float32(s.streamer.Len()) + s.IsOpen(&s.Visible). Flags(g.WindowFlagsNoResize). Size(mainWindowW, mainWindowH). @@ -91,7 +95,7 @@ func (s *SoundEditor) Build() { g.Row( hswidget.PlayPauseButton("##"+s.Path.GetUniqueID()+"playPause", &isPlaying, s.textureLoader). OnPlayClicked(s.play).OnPauseClicked(s.stop).Size(btnSize, btnSize), - g.ProgressBar(float32(s.streamer.Position())/float32(s.streamer.Len())).Size(-1, 24). + g.ProgressBar(progress).Size(-1, progressBarHeight). Overlay(fmt.Sprintf("%d:%02d / %d:%02d", secondsCurrent/progressIndicatorModifier, secondsCurrent%progressIndicatorModifier, diff --git a/hswindow/hstoolwindow/hsconsole/console.go b/hswindow/hstoolwindow/hsconsole/console.go index b1c4b4cf..5b81275c 100644 --- a/hswindow/hstoolwindow/hsconsole/console.go +++ b/hswindow/hstoolwindow/hsconsole/console.go @@ -1,4 +1,4 @@ -// Package hsconsole contains project's console +// Package hsconsole provides a graphical console for logging output while the app is running. package hsconsole import ( diff --git a/hswindow/hstoolwindow/hsmpqexplorer/mpqexplorer.go b/hswindow/hstoolwindow/hsmpqexplorer/mpqexplorer.go index d664e608..4f0d3e31 100644 --- a/hswindow/hstoolwindow/hsmpqexplorer/mpqexplorer.go +++ b/hswindow/hstoolwindow/hsmpqexplorer/mpqexplorer.go @@ -1,4 +1,5 @@ -// Package hsmpqexplorer contains mpq explorer's data +// Package hsmpqexplorer contains an implementation of a MPQ archive explorer, +// which displays the archive contents as a tree. package hsmpqexplorer import ( diff --git a/hswindow/hstoolwindow/hsprojectexplorer/projectexplorer.go b/hswindow/hstoolwindow/hsprojectexplorer/projectexplorer.go index e30cf673..fedb125f 100644 --- a/hswindow/hstoolwindow/hsprojectexplorer/projectexplorer.go +++ b/hswindow/hstoolwindow/hsprojectexplorer/projectexplorer.go @@ -1,4 +1,4 @@ -// Package hsprojectexplorer contains project explorer's data +// Package hsprojectexplorer provides a project explorer, for viewing project directories as trees. package hsprojectexplorer import (