Added ability to import and export town/city settings
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
@@ -806,6 +807,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create UI elements for error display and action buttons
|
// Create UI elements for error display and action buttons
|
||||||
|
var applySettingsToUI func(*Settings)
|
||||||
errorLabel := widget.NewLabel("")
|
errorLabel := widget.NewLabel("")
|
||||||
errorLabel.Wrapping = fyne.TextWrapWord
|
errorLabel.Wrapping = fyne.TextWrapWord
|
||||||
errorLabel.Hide()
|
errorLabel.Hide()
|
||||||
@@ -825,6 +827,12 @@ func main() {
|
|||||||
exportMasksBtn := widget.NewButton("Export Masks", func() {
|
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)
|
||||||
})
|
})
|
||||||
|
exportSettingsBtn := widget.NewButton("Export Settings", func() {
|
||||||
|
showSettingsExportDialog(w, settings)
|
||||||
|
})
|
||||||
|
importSettingsBtn := widget.NewButton("Import Settings", func() {
|
||||||
|
showSettingsImportDialog(w, settings, applySettingsToUI)
|
||||||
|
})
|
||||||
// Main generation button and logic
|
// Main generation button and logic
|
||||||
generateBtn = widget.NewButton("Generate", func() {
|
generateBtn = widget.NewButton("Generate", func() {
|
||||||
go func() {
|
go func() {
|
||||||
@@ -1465,6 +1473,82 @@ func main() {
|
|||||||
ratioContainer.Hide()
|
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(
|
buildingsTab := container.NewTabItem("Buildings", container.NewVBox(
|
||||||
numBuildingsSlider,
|
numBuildingsSlider,
|
||||||
minBuildingSizeSlider,
|
minBuildingSizeSlider,
|
||||||
@@ -1491,6 +1575,8 @@ func main() {
|
|||||||
exportHeightmapBtn,
|
exportHeightmapBtn,
|
||||||
exportBumpmapBtn,
|
exportBumpmapBtn,
|
||||||
exportMasksBtn,
|
exportMasksBtn,
|
||||||
|
exportSettingsBtn,
|
||||||
|
importSettingsBtn,
|
||||||
))
|
))
|
||||||
// Create the main layout using a horizontal split
|
// Create the main layout using a horizontal split
|
||||||
tabs := container.NewAppTabs(
|
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.
|
// showSaveDialog displays a dialog for saving an image.
|
||||||
func showSaveDialog(win fyne.Window, img image.Image, settings *Settings) {
|
func showSaveDialog(win fyne.Window, img image.Image, settings *Settings) {
|
||||||
// Create UI elements for the save dialog
|
// Create UI elements for the save dialog
|
||||||
|
|||||||
+10
-2
@@ -196,6 +196,16 @@ func LoadSettings() (*Settings, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeSettings(&settings, rawKeys)
|
||||||
|
|
||||||
|
return &settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSettings(settings *Settings, rawKeys map[string]json.RawMessage) {
|
||||||
|
if rawKeys == nil {
|
||||||
|
rawKeys = map[string]json.RawMessage{}
|
||||||
|
}
|
||||||
|
|
||||||
if settings.LakeShape == "" {
|
if settings.LakeShape == "" {
|
||||||
settings.LakeShape = "circle"
|
settings.LakeShape = "circle"
|
||||||
}
|
}
|
||||||
@@ -322,6 +332,4 @@ func LoadSettings() (*Settings, error) {
|
|||||||
}
|
}
|
||||||
settings.LastExportPath = homeDir
|
settings.LastExportPath = homeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
return &settings, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user