Added ability to import and export town/city settings
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user