diff --git a/go.mod b/go.mod index d8c07d0..4ac0080 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.25.6 require ( fyne.io/fyne/v2 v2.7.2 github.com/aquilax/go-perlin v1.1.0 + github.com/chai2010/webp v1.4.0 github.com/disintegration/imaging v1.6.2 github.com/ojrac/opensimplex-go v1.0.2 ) @@ -37,9 +38,9 @@ require ( github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/yuin/goldmark v1.7.8 // indirect - golang.org/x/image v0.24.0 // indirect + golang.org/x/image v0.35.0 // indirect golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 564d1d4..bb02d7d 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= +github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko= +github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -72,15 +74,15 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= -golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= +golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= +golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index 4b2db96..bb26be3 100644 --- a/main.go +++ b/main.go @@ -12,15 +12,21 @@ import ( "os" "path/filepath" + "image/jpeg" + "strings" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" + + "github.com/chai2010/webp" ) func main() { - a := app.New() + a := app.NewWithID("com.example.rpgcitymaker") a.Settings().SetTheme(&CustomTheme{a.Settings().Theme()}) w := a.NewWindow("RPG City Maker") w.Resize(fyne.NewSize(800, 600)) @@ -254,6 +260,9 @@ func main() { var generateBtn *widget.Button progressBar := NewTextOverlayProgressBar() + exportBtn := widget.NewButton("Export", func() { + showSaveDialog(w, canvasImg.Image, settings) + }) generateBtn = widget.NewButton("Generate", func() { go func() { @@ -437,6 +446,8 @@ func main() { generateBtn, errorLabel, progressBar, + widget.NewSeparator(), + exportBtn, )) tabs := container.NewAppTabs( @@ -464,3 +475,86 @@ func main() { w.SetContent(split) w.ShowAndRun() } + +func showSaveDialog(win fyne.Window, img image.Image, settings *Settings) { + fileNameEntry := widget.NewEntry() + fileNameEntry.SetPlaceHolder("image") + + formatSelect := widget.NewSelect([]string{"PNG", "JPG", "WEBP"}, nil) + formatSelect.SetSelected("PNG") + + 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, + widget.NewLabel("Format:"), + formatSelect, + ) + + saveDialog := dialog.NewCustom("Export Image", "Cancel", content, win) + + saveBtn := widget.NewButton("Save", func() { + fileName := fileNameEntry.Text + if fileName == "" { + return + } + + filePath := filepath.Join(pathLabel.Text, fileName+"."+strings.ToLower(formatSelect.Selected)) + settings.LastExportPath = filepath.Dir(filePath) + + file, err := os.Create(filePath) + if err != nil { + log.Println("Error creating file:", err) + return + } + defer file.Close() + + switch formatSelect.Selected { + case "PNG": + err = png.Encode(file, img) + case "JPG": + err = jpeg.Encode(file, img, nil) + case "WEBP": + err = webp.Encode(file, img, &webp.Options{Lossless: true}) + } + if err != nil { + log.Println("Failed to encode image:", err) + } + saveDialog.Hide() + }) + saveBtn.Disable() + + fileNameEntry.OnChanged = func(text string) { + if text == "" { + saveBtn.Disable() + } else { + saveBtn.Enable() + } + } + + saveDialog.SetButtons([]fyne.CanvasObject{ + widget.NewButton("Cancel", func() { + saveDialog.Hide() + }), + saveBtn, + }) + + saveDialog.Show() +} diff --git a/settings.go b/settings.go index f030355..fda342c 100644 --- a/settings.go +++ b/settings.go @@ -24,6 +24,7 @@ type Settings struct { MinRiverWidth float64 `json:"min_river_width"` MaxRiverWidth float64 `json:"max_river_width"` RiverCurvyness float64 `json:"river_curvyness"` + LastExportPath string `json:"last_export_path"` } func (s *Settings) Save() error { @@ -58,6 +59,10 @@ func LoadSettings() (*Settings, error) { file, err := os.Open(configFile) if err != nil { if os.IsNotExist(err) { + homeDir, err := os.UserHomeDir() + if err != nil { + homeDir = "." + } return &Settings{ Detail: 1, Roughness: 0, @@ -75,6 +80,7 @@ func LoadSettings() (*Settings, error) { MinRiverWidth: 1, MaxRiverWidth: 5, RiverCurvyness: 50, + LastExportPath: homeDir, }, nil } return nil, err @@ -86,5 +92,14 @@ func LoadSettings() (*Settings, error) { if err := decoder.Decode(&settings); err != nil { return nil, err } + + if settings.LastExportPath == "" { + homeDir, err := os.UserHomeDir() + if err != nil { + homeDir = "." + } + settings.LastExportPath = homeDir + } + return &settings, nil }