Added ability to import and export town/city settings

This commit is contained in:
grimsace
2026-03-11 13:08:40 -05:00
parent e9a7313a97
commit 51039399ec
2 changed files with 214 additions and 2 deletions
+204
View File
@@ -5,6 +5,7 @@ import (
"archive/zip"
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"image"
"image/color"
@@ -806,6 +807,7 @@ func main() {
}
// Create UI elements for error display and action buttons
var applySettingsToUI func(*Settings)
errorLabel := widget.NewLabel("")
errorLabel.Wrapping = fyne.TextWrapWord
errorLabel.Hide()
@@ -825,6 +827,12 @@ func main() {
exportMasksBtn := widget.NewButton("Export Masks", func() {
showMasksSaveDialog(w, canvasImg.Image, heightmapImg.Image, bumpmapImg.Image, settings, lakes, riverMask, treeMask, roadMask, bridgeMask, wallMask, turretMask, buildingMask)
})
exportSettingsBtn := widget.NewButton("Export Settings", func() {
showSettingsExportDialog(w, settings)
})
importSettingsBtn := widget.NewButton("Import Settings", func() {
showSettingsImportDialog(w, settings, applySettingsToUI)
})
// Main generation button and logic
generateBtn = widget.NewButton("Generate", func() {
go func() {
@@ -1465,6 +1473,82 @@ func main() {
ratioContainer.Hide()
}
applySettingsToUI = func(imported *Settings) {
lastExportPath := settings.LastExportPath
imageViewState := settings.ImageViewState
*settings = *imported
settings.LastExportPath = lastExportPath
settings.ImageViewState = imageViewState
if settings.BuildingShapeRatios == nil {
settings.BuildingShapeRatios = map[string]float64{
"squares": 40,
"circles": 30,
"rectangles": 30,
}
}
widthEntry.SetText(strconv.Itoa(settings.Width))
heightEntry.SetText(strconv.Itoa(settings.Height))
seedEntry.SetText(strconv.FormatInt(settings.Seed, 10))
detailSlider.value.Set(settings.Detail)
roughnessSlider.value.Set(settings.Roughness)
lakesSlider.value.Set(float64(settings.Lakes))
lakeSizeLowerSlider.value.Set(settings.LakeSizeLower)
lakeSizeUpperSlider.value.Set(settings.LakeSizeUpper)
lakeEdgeRoughnessSlider.value.Set(settings.LakeEdgeRoughness)
lakeShapeSelect.SetSelected(settings.LakeShape)
riversSlider.value.Set(float64(settings.Rivers))
minRiverWidthSlider.value.Set(settings.MinRiverWidth)
maxRiverWidthSlider.value.Set(settings.MaxRiverWidth)
riverCurvynessSlider.value.Set(settings.RiverCurvyness)
riverWidthVariabilitySlider.value.Set(settings.RiverWidthVariability)
riverEdgeRoughnessSlider.value.Set(settings.RiverEdgeRoughness)
minTreeSizeSlider.value.Set(settings.MinTreeSize)
maxTreeSizeSlider.value.Set(settings.MaxTreeSize)
treeCoverageSlider.value.Set(settings.TreeCoverage)
treeClumpinessSlider.value.Set(settings.TreeClumpiness)
minRoadWidthSlider.value.Set(settings.MinRoadWidth)
maxRoadWidthSlider.value.Set(settings.MaxRoadWidth)
buildingsPerRoadSlider.value.Set(float64(settings.BuildingsPerRoad))
roadExitsSlider.value.Set(float64(settings.RoadExits))
roadCurvynessSlider.value.Set(settings.RoadCurvyness)
roadDistributionSlider.value.Set(settings.RoadDistribution)
minRoadAngleSlider.value.Set(settings.MinRoadAngle)
minWallWidthSlider.value.Set(settings.MinWallWidth)
maxWallWidthSlider.value.Set(settings.MaxWallWidth)
numWallsSlider.value.Set(float64(settings.NumWalls))
cityCoverageSlider.value.Set(settings.CityCoverage)
wallCurvynessSlider.value.Set(settings.WallCurvyness)
gateSpacingSlider.value.Set(settings.GateSpacing)
showTurretsCheck.SetChecked(settings.ShowTurrets)
turretSizeSlider.value.Set(settings.TurretSize)
turretShapeSelect.SetSelected(settings.TurretShape)
turretSpacingSlider.value.Set(settings.TurretSpacing)
numBuildingsSlider.value.Set(float64(settings.NumBuildings))
minBuildingSizeSlider.value.Set(settings.MinBuildingSize)
maxBuildingSizeSlider.value.Set(settings.MaxBuildingSize)
buildingDistributionSlider.value.Set(settings.BuildingDistribution)
buildingShapeSelect.SetSelected(settings.BuildingShape)
updateRatioSliders()
minBuildingComplexitySlider.value.Set(float64(settings.MinBuildingComplexity))
maxBuildingComplexitySlider.value.Set(float64(settings.MaxBuildingComplexity))
buildingComplexityRatioSlider.value.Set(settings.BuildingComplexityRatio)
for key := range errorStates {
errorStates[key] = false
}
errorLabel.Hide()
updateGenerateBtnState()
}
buildingsTab := container.NewTabItem("Buildings", container.NewVBox(
numBuildingsSlider,
minBuildingSizeSlider,
@@ -1491,6 +1575,8 @@ func main() {
exportHeightmapBtn,
exportBumpmapBtn,
exportMasksBtn,
exportSettingsBtn,
importSettingsBtn,
))
// Create the main layout using a horizontal split
tabs := container.NewAppTabs(
@@ -1789,6 +1875,124 @@ func saveImage(img image.Image, path string) {
}
}
func decodeSettingsJSON(raw []byte) (*Settings, error) {
var imported Settings
if err := json.Unmarshal(raw, &imported); err != nil {
return nil, err
}
var rawKeys map[string]json.RawMessage
if err := json.Unmarshal(raw, &rawKeys); err != nil {
return nil, err
}
normalizeSettings(&imported, rawKeys)
return &imported, nil
}
func saveSettingsToFile(path string, settings *Settings) error {
exportSettings := *settings
exportSettings.LastExportPath = ""
exportSettings.ImageViewState = 0
data, err := json.MarshalIndent(&exportSettings, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, data, 0644)
}
func showSettingsExportDialog(win fyne.Window, settings *Settings) {
fileNameEntry := widget.NewEntry()
fileNameEntry.SetPlaceHolder("settings")
pathLabel := widget.NewLabel(settings.LastExportPath)
browseBtn := widget.NewButton("Browse", func() {
dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) {
if err != nil {
log.Println("Error opening folder dialog:", err)
return
}
if uri == nil {
return
}
settings.LastExportPath = uri.Path()
pathLabel.SetText(settings.LastExportPath)
}, win)
})
content := container.NewVBox(
widget.NewLabel("Save to:"),
container.NewBorder(nil, nil, nil, browseBtn, pathLabel),
widget.NewLabel("Filename:"),
fileNameEntry,
)
saveDialog := dialog.NewCustom("Export Settings", "Cancel", content, win)
saveBtn := widget.NewButton("Save", func() {
fileName := strings.TrimSpace(fileNameEntry.Text)
if fileName == "" {
return
}
if !strings.HasSuffix(strings.ToLower(fileName), ".json") {
fileName += ".json"
}
filePath := filepath.Join(pathLabel.Text, fileName)
if err := saveSettingsToFile(filePath, settings); err != nil {
dialog.ShowError(err, win)
return
}
settings.LastExportPath = filepath.Dir(filePath)
saveDialog.Hide()
})
saveBtn.Disable()
fileNameEntry.OnChanged = func(text string) {
if strings.TrimSpace(text) == "" {
saveBtn.Disable()
} else {
saveBtn.Enable()
}
}
saveDialog.SetButtons([]fyne.CanvasObject{
widget.NewButton("Cancel", func() {
saveDialog.Hide()
}),
saveBtn,
})
saveDialog.Show()
}
func showSettingsImportDialog(win fyne.Window, settings *Settings, apply func(*Settings)) {
dialog.ShowFileOpen(func(rc fyne.URIReadCloser, err error) {
if err != nil {
dialog.ShowError(err, win)
return
}
if rc == nil {
return
}
defer rc.Close()
raw, err := io.ReadAll(rc)
if err != nil {
dialog.ShowError(err, win)
return
}
imported, err := decodeSettingsJSON(raw)
if err != nil {
dialog.ShowError(err, win)
return
}
if uri := rc.URI(); uri != nil {
settings.LastExportPath = filepath.Dir(uri.Path())
}
apply(imported)
}, win)
}
// showSaveDialog displays a dialog for saving an image.
func showSaveDialog(win fyne.Window, img image.Image, settings *Settings) {
// Create UI elements for the save dialog