added requested add text feature

This commit is contained in:
grimsace
2026-03-30 11:23:01 -05:00
parent fb53ae779d
commit 9e841fcced
3 changed files with 566 additions and 15 deletions
+172 -10
View File
@@ -50,6 +50,11 @@ func main() {
canvasImg := &canvas.Image{
FillMode: canvas.ImageFillContain,
}
textPreviewImg := &canvas.Image{
FillMode: canvas.ImageFillStretch,
}
textPreviewImg.Hide()
textPreviewLayer := container.NewWithoutLayout(textPreviewImg)
heightmapImg := &canvas.Image{
FillMode: canvas.ImageFillContain,
}
@@ -69,6 +74,22 @@ func main() {
var exitRoadMask *PixelMask
var wallMask *PixelMask
var turretMask *PixelMask
var canvasBaseImage image.Image
textLayer := newTextLayer(settings.Width, settings.Height)
var pendingText *PendingTextPlacement
refreshCanvasDisplay := func() {
if canvasBaseImage == nil {
canvasImg.Image = nil
} else {
canvasImg.Image = composeCanvasWithText(canvasBaseImage, textLayer, nil)
}
canvasImg.Refresh()
}
hideTextPreview := func() {
textPreviewImg.Image = nil
textPreviewImg.Hide()
textPreviewImg.Refresh()
}
// Set up application configuration directory
configDir, err := os.UserConfigDir()
@@ -88,7 +109,7 @@ func main() {
}
closing = true
canvasSnapshot := canvasImg.Image
canvasSnapshot := canvasBaseImage
heightmapSnapshot := heightmapImg.Image
bumpmapSnapshot := bumpmapImg.Image
@@ -127,7 +148,10 @@ func main() {
defer canvasFile.Close()
img, err := png.Decode(canvasFile)
if err == nil {
canvasImg.Image = img
canvasBaseImage = img
textLayer.Clear(img.Bounds().Dx(), img.Bounds().Dy())
hideTextPreview()
refreshCanvasDisplay()
}
}
@@ -351,7 +375,11 @@ func main() {
heightmapImg.Image = compositeImg
bumpmapImg.Image = bumpMap
canvasImg.Image = finalImage
canvasBaseImage = finalImage
textLayer.Clear(settings.Width, settings.Height)
pendingText = nil
hideTextPreview()
refreshCanvasDisplay()
}
var generateBtn *widget.Button
@@ -845,7 +873,7 @@ func main() {
showSaveDialog(w, bumpmapImg.Image, settings)
})
exportMasksBtn := widget.NewButton("Export Masks", func() {
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, bumpmapImg.Image, settings, lakes, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask)
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, bumpmapImg.Image, settings, lakes, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask, textLayer.Mask)
})
exportSettingsBtn := widget.NewButton("Export Settings", func() {
showSettingsExportDialog(w, settings)
@@ -1144,8 +1172,11 @@ func main() {
heightmapImg.Refresh()
bumpmapImg.Image = bumpMap
bumpmapImg.Refresh()
canvasImg.Image = finalImage
canvasImg.Refresh()
canvasBaseImage = finalImage
textLayer.Clear(settings.Width, settings.Height)
pendingText = nil
hideTextPreview()
refreshCanvasDisplay()
if len(timedOutSteps) > 0 {
errorLabel.SetText("Generation timed out after 1 minute for: " + strings.Join(timedOutSteps, ", ") + ". Partial results were used.")
errorLabel.Show()
@@ -1757,6 +1788,77 @@ func main() {
removePresetBtn.Disable()
refreshPresetOptions("No Preset")
textFonts := loadSystemFonts()
textFontNames := make([]string, 0, len(textFonts))
fontPathByName := make(map[string]string, len(textFonts))
for _, fontInfo := range textFonts {
textFontNames = append(textFontNames, fontInfo.Name)
fontPathByName[fontInfo.Name] = fontInfo.Path
}
selectedTextFont := textFontNames[0]
selectedTextColor := "black"
textSizeSlider := newNumericInputSliderWithStep(minTextSizePercent, maxTextSizePercent, 2.0, textSizePercentStep, "%.1f%%", "Font Size")
textFontLabel := widget.NewLabel("Font:")
textFontSelect := widget.NewSelect(textFontNames, func(s string) {
if s != "" {
selectedTextFont = s
}
})
textFontSelect.SetSelected(selectedTextFont)
textColorLabel := widget.NewLabel("Font Color:")
textColorSelect := widget.NewSelect([]string{"white", "grey", "black"}, func(s string) {
if s != "" {
selectedTextColor = s
}
})
textColorSelect.SetSelected(selectedTextColor)
textEntry := widget.NewEntry()
textEntry.SetPlaceHolder("Enter text to place on the map")
textInstructions := widget.NewLabel("Click Add to start placement. Move the mouse over the canvas image to preview the text at 50% opacity. Left click places it on the canvas. Right click cancels.")
textInstructions.Wrapping = fyne.TextWrapWord
addTextBtn := widget.NewButton("Add", func() {
if canvasBaseImage == nil {
textInstructions.SetText("Generate a canvas before placing text.")
return
}
content := strings.TrimSpace(textEntry.Text)
if content == "" {
textInstructions.SetText("Enter text before clicking Add.")
return
}
sizeVal, _ := textSizeSlider.value.Get()
pendingText = &PendingTextPlacement{
TextPlacementRequest: TextPlacementRequest{
Content: content,
FontName: selectedTextFont,
FontPath: fontPathByName[selectedTextFont],
SizePercent: sizeVal,
ColorName: selectedTextColor,
},
Visible: false,
}
pendingText.Preview, _ = renderTextSprite(pendingText.TextPlacementRequest, 128, settings.Width, settings.Height)
textInstructions.SetText("Placement active on the canvas. Left click places the text. Right click cancels.")
hideTextPreview()
})
clearTextBtn := widget.NewButton("Clear Text", func() {
textLayer.Clear(settings.Width, settings.Height)
pendingText = nil
textInstructions.SetText("All placed text has been cleared.")
hideTextPreview()
refreshCanvasDisplay()
})
textTab := container.NewTabItem("Text", container.NewVBox(
textSizeSlider,
textFontLabel,
textFontSelect,
textColorLabel,
textColorSelect,
container.NewBorder(nil, nil, nil, addTextBtn, textEntry),
textInstructions,
clearTextBtn,
))
buildingsTab := container.NewTabItem("Buildings", container.NewVBox(
numBuildingsSlider,
minBuildingSizeSlider,
@@ -1797,6 +1899,7 @@ func main() {
waterTab,
roadsTab,
fortificationsTab,
textTab,
buildingsTab,
)
@@ -1834,7 +1937,27 @@ func main() {
right.Refresh()
}
tappableCanvas = newTappableImage(container.NewMax(canvasImg), func() {
tappableCanvas = newTappableImage(container.NewMax(canvasImg, textPreviewLayer), func(ev *fyne.PointEvent) {
if pendingText != nil {
point, ok := mapCanvasPositionToImagePoint(ev.Position, tappableCanvas.Size(), canvasBaseImage)
if !ok {
return
}
pendingText.Position = point
pendingText.Visible = true
if err := textLayer.Add(PlacedText{
TextPlacementRequest: pendingText.TextPlacementRequest,
Position: pendingText.Position,
}); err != nil {
textInstructions.SetText("Failed to place text with the selected font.")
return
}
pendingText = nil
textInstructions.SetText("Text placed on the canvas.")
hideTextPreview()
refreshCanvasDisplay()
return
}
if settings.ImageViewState == 1 {
settings.ImageViewState = 0
} else {
@@ -1842,7 +1965,45 @@ func main() {
}
updateRightPanel()
})
tappableHeightmap = newTappableImage(container.NewMax(heightmapImg), func() {
tappableCanvas.onSecondaryTap = func(*fyne.PointEvent) {
if pendingText == nil {
return
}
pendingText = nil
textInstructions.SetText("Text placement cancelled.")
hideTextPreview()
refreshCanvasDisplay()
}
tappableCanvas.onHover = func(pos fyne.Position) {
if pendingText == nil || canvasBaseImage == nil {
return
}
point, ok := mapCanvasPositionToImagePoint(pos, tappableCanvas.Size(), canvasBaseImage)
pendingText.Visible = ok
if ok {
pendingText.Position = point
if pendingText.Preview != nil {
canvasPos, canvasSize, ok := mapImagePointToCanvasRect(point, pendingText.Preview, tappableCanvas.Size(), canvasBaseImage)
if ok {
textPreviewImg.Image = pendingText.Preview
textPreviewImg.Move(canvasPos)
textPreviewImg.Resize(canvasSize)
textPreviewImg.Show()
textPreviewImg.Refresh()
return
}
}
}
hideTextPreview()
}
tappableCanvas.onHoverEnd = func() {
if pendingText == nil {
return
}
pendingText.Visible = false
hideTextPreview()
}
tappableHeightmap = newTappableImage(container.NewMax(heightmapImg), func(*fyne.PointEvent) {
if settings.ImageViewState == 2 {
settings.ImageViewState = 0
} else {
@@ -1850,7 +2011,7 @@ func main() {
}
updateRightPanel()
})
tappableBumpmap = newTappableImage(container.NewMax(bumpmapImg), func() {
tappableBumpmap = newTappableImage(container.NewMax(bumpmapImg), func(*fyne.PointEvent) {
if settings.ImageViewState == 3 {
settings.ImageViewState = 0
} else {
@@ -1910,7 +2071,7 @@ func encodeImageToWriter(w io.Writer, img image.Image, format string) error {
}
// showMasksSaveDialog displays a dialog for saving the generated masks.
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg image.Image, settings *Settings, lakes [][]image.Point, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask *PixelMask) {
func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg image.Image, settings *Settings, lakes [][]image.Point, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask, textMask *PixelMask) {
// Create UI elements for the save dialog
fileNameEntry := widget.NewEntry()
fileNameEntry.SetPlaceHolder("masks_folder")
@@ -1997,6 +2158,7 @@ func showMasksSaveDialog(win fyne.Window, canvasImg, heightmapImg, bumpmapImg im
{name: "walls_mask." + imgFormat, make: func() image.Image { return maskToGray(wallMask) }},
{name: "turrets_mask." + imgFormat, make: func() image.Image { return maskToGray(turretMask) }},
{name: "buildings_mask." + imgFormat, make: func() image.Image { return maskToGray(buildingMask) }},
{name: "text_mask." + imgFormat, make: func() image.Image { return maskToGray(textMask) }},
}
// Save the images based on the selected packaging option