added requested add text feature
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user