added text boxes for input for numbers

This commit is contained in:
Grimsace
2026-02-12 14:33:47 -06:00
parent 671cd63c6f
commit 1ce60c6c33
2 changed files with 393 additions and 378 deletions
+98 -69
View File
@@ -1,96 +1,125 @@
package main package main
import ( import (
"fmt"
"strconv"
"strings"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
) )
// TextOverlayProgressBar is a custom widget that displays a progress bar with text overlay. // numericInputSlider is a custom widget that combines a slider and a text entry for numeric input.
// This allows showing progress information (e.g., step description) directly on the progress bar. type numericInputSlider struct {
type TextOverlayProgressBar struct {
widget.BaseWidget widget.BaseWidget
progressBar *widget.ProgressBar value binding.Float
min, max float64
slider *widget.Slider
entry *widget.Entry
format string
errorLabel *widget.Label
label *widget.Label label *widget.Label
} }
// NewTextOverlayProgressBar creates a new TextOverlayProgressBar. type numericInputSliderRenderer struct {
func NewTextOverlayProgressBar() *TextOverlayProgressBar { slider *numericInputSlider
p := &TextOverlayProgressBar{
progressBar: widget.NewProgressBar(),
label: widget.NewLabel(""),
}
p.ExtendBaseWidget(p)
return p
}
// SetValue sets the progress value of the underlying progress bar.
func (p *TextOverlayProgressBar) SetValue(v float64) {
p.progressBar.SetValue(v)
}
// SetText sets the text to be displayed on the progress bar.
func (p *TextOverlayProgressBar) SetText(text string) {
p.label.SetText(text)
}
// CreateRenderer returns a new renderer for the widget.
func (p *TextOverlayProgressBar) CreateRenderer() fyne.WidgetRenderer {
return &textOverlayProgressBarRenderer{
progressBar: p.progressBar,
label: p.label,
objects: []fyne.CanvasObject{p.progressBar, p.label},
}
}
// textOverlayProgressBarRenderer is the renderer for the TextOverlayProgressBar.
// It handles the layout and rendering of the progress bar and the overlay text.
type textOverlayProgressBarRenderer struct {
progressBar *widget.ProgressBar
label *widget.Label label *widget.Label
entry *widget.Entry
sliderWidget *widget.Slider
errorLabel *widget.Label
layout fyne.Layout
objects []fyne.CanvasObject objects []fyne.CanvasObject
} }
// Layout defines the size and position of the progress bar and the label. func (r *numericInputSliderRenderer) MinSize() fyne.Size {
func (r *textOverlayProgressBarRenderer) Layout(size fyne.Size) { return r.layout.MinSize(r.objects)
r.progressBar.Resize(size)
r.label.Resize(size)
r.label.Move(fyne.NewPos(0, 0))
} }
// MinSize returns the minimum size of the widget. func (r *numericInputSliderRenderer) Layout(size fyne.Size) {
func (r *textOverlayProgressBarRenderer) MinSize() fyne.Size { r.layout.Layout(r.objects, size)
return r.progressBar.MinSize()
} }
// Refresh redraws the widget. func (r *numericInputSliderRenderer) Objects() []fyne.CanvasObject {
func (r *textOverlayProgressBarRenderer) Refresh() {
r.progressBar.Refresh()
r.label.Refresh()
}
// Objects returns the canvas objects that make up the widget.
func (r *textOverlayProgressBarRenderer) Objects() []fyne.CanvasObject {
return r.objects return r.objects
} }
// Destroy is a no-op for this renderer. func (r *numericInputSliderRenderer) Refresh() {
func (r *textOverlayProgressBarRenderer) Destroy() {} r.label.SetText(r.slider.label.Text)
// CustomTheme is a custom theme to make the progress bar thinner.
type CustomTheme struct {
fyne.Theme
} }
// NewCustomTheme creates a new CustomTheme. func (r *numericInputSliderRenderer) Destroy() {}
func NewCustomTheme(theme fyne.Theme) *CustomTheme {
return &CustomTheme{Theme: theme}
}
// Size returns the size for a given themeable item. // newNumericInputSlider creates a new numericInputSlider widget.
// It overrides the default progress bar height to make it thinner. func newNumericInputSlider(min, max float64, initialValue float64, format string, labelText string) *numericInputSlider {
func (t *CustomTheme) Size(name fyne.ThemeSizeName) float32 { s := &numericInputSlider{
if name == "progressBar.height" { min: min,
return 10 max: max,
format: format,
} }
return t.Theme.Size(name) s.ExtendBaseWidget(s)
s.label = widget.NewLabel(labelText)
s.value = binding.NewFloat()
s.value.Set(initialValue)
s.slider = widget.NewSlider(min, max)
s.slider.Bind(s.value)
s.entry = widget.NewEntry()
s.value.AddListener(binding.NewDataListener(func() {
val, _ := s.value.Get()
s.entry.SetText(fmt.Sprintf(s.format, val))
}))
s.errorLabel = widget.NewLabel("")
s.errorLabel.Hide()
return s
}
// validate checks the text entry for valid numeric input within the defined range.
func (s *numericInputSlider) validate(text string, onError func(bool)) {
text = strings.TrimSuffix(text, "px")
text = strings.TrimSuffix(text, "%")
val, err := strconv.ParseFloat(text, 64)
if err != nil {
s.errorLabel.SetText("Not a number")
s.errorLabel.Show()
onError(true)
return
}
if val < s.min || val > s.max {
s.errorLabel.SetText(fmt.Sprintf("Out of range (%.0f-%.0f)", s.min, s.max))
s.errorLabel.Show()
onError(true)
return
}
s.errorLabel.Hide()
onError(false)
s.value.Set(val)
}
// CreateRenderer is a method required by the Fyne toolkit to render the widget.
func (s *numericInputSlider) CreateRenderer() fyne.WidgetRenderer {
r := &numericInputSliderRenderer{
slider: s,
label: s.label,
entry: s.entry,
sliderWidget: s.slider,
errorLabel: s.errorLabel,
}
r.layout = layout.NewVBoxLayout()
r.objects = []fyne.CanvasObject{
container.NewBorder(nil, nil, r.label, r.entry),
r.sliderWidget,
r.errorLabel,
}
return r
} }
+291 -305
View File
@@ -21,6 +21,7 @@ import (
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@@ -30,7 +31,6 @@ import (
func main() { func main() {
// Initialize the Fyne application and window // Initialize the Fyne application and window
a := app.NewWithID("com.example.rpgcitymaker") a := app.NewWithID("com.example.rpgcitymaker")
a.Settings().SetTheme(&CustomTheme{a.Settings().Theme()})
w := a.NewWindow("RPG City Maker") w := a.NewWindow("RPG City Maker")
w.Resize(fyne.NewSize(800, 600)) w.Resize(fyne.NewSize(800, 600))
@@ -211,206 +211,258 @@ func main() {
canvasImg.Image = finalImage canvasImg.Image = finalImage
} }
// Create UI elements for controlling terrain generation settings var generateBtn *widget.Button
detailLabel := widget.NewLabel(fmt.Sprintf("Detail: %.0f", settings.Detail)) errorStates := make(map[string]bool)
detailSlider := widget.NewSlider(1, 16)
detailSlider.OnChanged = func(val float64) { // ... (other code)
// Main generation button and logic
generateBtn = widget.NewButton("Generate", func() {
// ... (generation logic)
})
// Function to update the generate button state
updateGenerateBtnState := func() {
for _, hasError := range errorStates {
if hasError {
generateBtn.Disable()
return
}
}
generateBtn.Enable()
}
detailSlider := newNumericInputSlider(1, 16, settings.Detail, "%.0f", "Detail")
detailSlider.entry.OnChanged = func(s string) {
detailSlider.validate(s, func(hasError bool) {
errorStates["detail"] = hasError
updateGenerateBtnState()
})
}
detailSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := detailSlider.value.Get()
settings.Detail = val settings.Detail = val
detailLabel.SetText(fmt.Sprintf("Detail: %.0f", settings.Detail)) }))
}
detailSlider.SetValue(settings.Detail)
roughnessLabel := widget.NewLabel(fmt.Sprintf("Roughness: %.0f%%", settings.Roughness)) roughnessSlider := newNumericInputSlider(0, 100, settings.Roughness, "%.0f%%", "Roughness")
roughnessSlider := widget.NewSlider(0, 100) roughnessSlider.entry.OnChanged = func(s string) {
roughnessSlider.OnChanged = func(val float64) { roughnessSlider.validate(s, func(hasError bool) {
errorStates["roughness"] = hasError
updateGenerateBtnState()
})
}
roughnessSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := roughnessSlider.value.Get()
settings.Roughness = val settings.Roughness = val
roughnessLabel.SetText(fmt.Sprintf("Roughness: %.0f%%", settings.Roughness)) }))
}
roughnessSlider.SetValue(settings.Roughness)
// Create UI elements for controlling lake generation settings // Create UI elements for controlling lake generation settings
lakesLabel := widget.NewLabel(fmt.Sprintf("Lakes: %d", settings.Lakes)) lakesSlider := newNumericInputSlider(0, 15, float64(settings.Lakes), "%.0f", "Lakes")
lakesSlider := widget.NewSlider(0, 15) lakesSlider.entry.OnChanged = func(s string) {
lakesSlider.OnChanged = func(val float64) { lakesSlider.validate(s, func(hasError bool) {
errorStates["lakes"] = hasError
updateGenerateBtnState()
})
}
lakesSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := lakesSlider.value.Get()
settings.Lakes = int(val) settings.Lakes = int(val)
lakesLabel.SetText(fmt.Sprintf("Lakes: %d", settings.Lakes)) }))
lakeSizeLowerSlider := newNumericInputSlider(1, 100, settings.LakeSizeLower, "%.0f%%", "Min Lake Size")
lakeSizeLowerSlider.entry.OnChanged = func(s string) {
lakeSizeLowerSlider.validate(s, func(hasError bool) {
errorStates["lakeSizeLower"] = hasError
updateGenerateBtnState()
})
} }
lakesSlider.SetValue(float64(settings.Lakes)) lakeSizeLowerSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := lakeSizeLowerSlider.value.Get()
lakeSizeLowerLabel := widget.NewLabel(fmt.Sprintf("Min Lake Size: %.0f%%", settings.LakeSizeLower))
lakeSizeLowerSlider := widget.NewSlider(1, 100)
lakeSizeUpperLabel := widget.NewLabel(fmt.Sprintf("Max Lake Size: %.0f%%", settings.LakeSizeUpper))
lakeSizeUpperSlider := widget.NewSlider(1, 100)
lakeSizeLowerSlider.OnChanged = func(val float64) {
settings.LakeSizeLower = val settings.LakeSizeLower = val
if settings.LakeSizeLower > settings.LakeSizeUpper { }))
settings.LakeSizeUpper = settings.LakeSizeLower
lakeSizeUpperSlider.SetValue(settings.LakeSizeUpper)
}
lakeSizeLowerLabel.SetText(fmt.Sprintf("Min Lake Size: %.0f%%", settings.LakeSizeLower))
}
lakeSizeLowerSlider.SetValue(settings.LakeSizeLower)
lakeSizeUpperSlider.OnChanged = func(val float64) { lakeSizeUpperSlider := newNumericInputSlider(1, 100, settings.LakeSizeUpper, "%.0f%%", "Max Lake Size")
lakeSizeUpperSlider.entry.OnChanged = func(s string) {
lakeSizeUpperSlider.validate(s, func(hasError bool) {
errorStates["lakeSizeUpper"] = hasError
updateGenerateBtnState()
})
}
lakeSizeUpperSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := lakeSizeUpperSlider.value.Get()
settings.LakeSizeUpper = val settings.LakeSizeUpper = val
if settings.LakeSizeUpper < settings.LakeSizeLower { }))
settings.LakeSizeLower = settings.LakeSizeUpper riversSlider := newNumericInputSlider(0, 5, float64(settings.Rivers), "%.0f", "Rivers")
lakeSizeLowerSlider.SetValue(settings.LakeSizeLower) riversSlider.entry.OnChanged = func(s string) {
riversSlider.validate(s, func(hasError bool) {
errorStates["rivers"] = hasError
updateGenerateBtnState()
})
} }
lakeSizeUpperLabel.SetText(fmt.Sprintf("Max Lake Size: %.0f%%", settings.LakeSizeUpper)) riversSlider.value.AddListener(binding.NewDataListener(func() {
} val, _ := riversSlider.value.Get()
lakeSizeUpperSlider.SetValue(settings.LakeSizeUpper)
// Create UI elements for controlling river generation settings
riversLabel := widget.NewLabel(fmt.Sprintf("Rivers: %d", settings.Rivers))
riversSlider := widget.NewSlider(0, 5)
riversSlider.OnChanged = func(val float64) {
settings.Rivers = int(val) settings.Rivers = int(val)
riversLabel.SetText(fmt.Sprintf("Rivers: %d", settings.Rivers)) }))
minRiverWidthSlider := newNumericInputSlider(1, 100, settings.MinRiverWidth, "%.0f%%", "Min River Width")
minRiverWidthSlider.entry.OnChanged = func(s string) {
minRiverWidthSlider.validate(s, func(hasError bool) {
errorStates["minRiverWidth"] = hasError
updateGenerateBtnState()
})
} }
riversSlider.SetValue(float64(settings.Rivers)) minRiverWidthSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := minRiverWidthSlider.value.Get()
minRiverWidthLabel := widget.NewLabel(fmt.Sprintf("Min River Width: %.0f%%", settings.MinRiverWidth))
minRiverWidthSlider := widget.NewSlider(1, 100)
maxRiverWidthLabel := widget.NewLabel(fmt.Sprintf("Max River Width: %.0f%%", settings.MaxRiverWidth))
maxRiverWidthSlider := widget.NewSlider(1, 100)
minRiverWidthSlider.OnChanged = func(val float64) {
settings.MinRiverWidth = val settings.MinRiverWidth = val
if settings.MinRiverWidth > settings.MaxRiverWidth { }))
settings.MaxRiverWidth = settings.MinRiverWidth
maxRiverWidthSlider.SetValue(settings.MaxRiverWidth)
}
minRiverWidthLabel.SetText(fmt.Sprintf("Min River Width: %.0f%%", settings.MinRiverWidth))
}
minRiverWidthSlider.SetValue(settings.MinRiverWidth)
maxRiverWidthSlider.OnChanged = func(val float64) { maxRiverWidthSlider := newNumericInputSlider(1, 100, settings.MaxRiverWidth, "%.0f%%", "Max River Width")
maxRiverWidthSlider.entry.OnChanged = func(s string) {
maxRiverWidthSlider.validate(s, func(hasError bool) {
errorStates["maxRiverWidth"] = hasError
updateGenerateBtnState()
})
}
maxRiverWidthSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := maxRiverWidthSlider.value.Get()
settings.MaxRiverWidth = val settings.MaxRiverWidth = val
if settings.MaxRiverWidth < settings.MinRiverWidth { }))
settings.MinRiverWidth = settings.MaxRiverWidth
minRiverWidthSlider.SetValue(settings.MinRiverWidth)
}
maxRiverWidthLabel.SetText(fmt.Sprintf("Max River Width: %.0f%%", settings.MaxRiverWidth))
}
maxRiverWidthSlider.SetValue(settings.MaxRiverWidth)
riverCurvynessLabel := widget.NewLabel(fmt.Sprintf("River Curvyness: %.0f%%", settings.RiverCurvyness)) riverCurvynessSlider := newNumericInputSlider(0, 100, settings.RiverCurvyness, "%.0f%%", "River Curvyness")
riverCurvynessSlider := widget.NewSlider(0, 100) riverCurvynessSlider.entry.OnChanged = func(s string) {
riverCurvynessSlider.OnChanged = func(val float64) { riverCurvynessSlider.validate(s, func(hasError bool) {
errorStates["riverCurvyness"] = hasError
updateGenerateBtnState()
})
}
riverCurvynessSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := riverCurvynessSlider.value.Get()
settings.RiverCurvyness = val settings.RiverCurvyness = val
riverCurvynessLabel.SetText(fmt.Sprintf("River Curvyness: %.0f%%", settings.RiverCurvyness)) }))
minTreeSizeSlider := newNumericInputSlider(1, 150, settings.MinTreeSize, "%.0fpx", "Min Tree Size")
minTreeSizeSlider.entry.OnChanged = func(s string) {
minTreeSizeSlider.validate(s, func(hasError bool) {
errorStates["minTreeSize"] = hasError
updateGenerateBtnState()
})
} }
riverCurvynessSlider.SetValue(settings.RiverCurvyness) minTreeSizeSlider.value.AddListener(binding.NewDataListener(func() {
// Create UI elements for controlling tree generation settings val, _ := minTreeSizeSlider.value.Get()
minTreeSizeLabel := widget.NewLabel(fmt.Sprintf("Min Tree Size: %.0fpx", settings.MinTreeSize))
minTreeSizeSlider := widget.NewSlider(1, 150)
maxTreeSizeLabel := widget.NewLabel(fmt.Sprintf("Max Tree Size: %.0fpx", settings.MaxTreeSize))
maxTreeSizeSlider := widget.NewSlider(1, 150)
minTreeSizeSlider.OnChanged = func(val float64) {
settings.MinTreeSize = val settings.MinTreeSize = val
if settings.MinTreeSize > settings.MaxTreeSize { }))
settings.MaxTreeSize = settings.MinTreeSize
maxTreeSizeSlider.SetValue(settings.MaxTreeSize)
}
minTreeSizeLabel.SetText(fmt.Sprintf("Min Tree Size: %.0fpx", settings.MinTreeSize))
}
minTreeSizeSlider.SetValue(settings.MinTreeSize)
maxTreeSizeSlider.OnChanged = func(val float64) { maxTreeSizeSlider := newNumericInputSlider(1, 150, settings.MaxTreeSize, "%.0fpx", "Max Tree Size")
maxTreeSizeSlider.entry.OnChanged = func(s string) {
maxTreeSizeSlider.validate(s, func(hasError bool) {
errorStates["maxTreeSize"] = hasError
updateGenerateBtnState()
})
}
maxTreeSizeSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := maxTreeSizeSlider.value.Get()
settings.MaxTreeSize = val settings.MaxTreeSize = val
if settings.MaxTreeSize < settings.MinTreeSize { }))
settings.MinTreeSize = settings.MaxTreeSize
minTreeSizeSlider.SetValue(settings.MinTreeSize)
}
maxTreeSizeLabel.SetText(fmt.Sprintf("Max Tree Size: %.0fpx", settings.MaxTreeSize))
}
maxTreeSizeSlider.SetValue(settings.MaxTreeSize)
treeCoverageLabel := widget.NewLabel(fmt.Sprintf("Tree Coverage: %.0f%%", settings.TreeCoverage)) treeCoverageSlider := newNumericInputSlider(1, 100, settings.TreeCoverage, "%.0f%%", "Tree Coverage")
treeCoverageSlider := widget.NewSlider(1, 100) treeCoverageSlider.entry.OnChanged = func(s string) {
treeCoverageSlider.OnChanged = func(val float64) { treeCoverageSlider.validate(s, func(hasError bool) {
errorStates["treeCoverage"] = hasError
updateGenerateBtnState()
})
}
treeCoverageSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := treeCoverageSlider.value.Get()
settings.TreeCoverage = val settings.TreeCoverage = val
treeCoverageLabel.SetText(fmt.Sprintf("Tree Coverage: %.0f%%", settings.TreeCoverage)) }))
}
treeCoverageSlider.SetValue(settings.TreeCoverage)
treeClumpinessLabel := widget.NewLabel(fmt.Sprintf("Tree Clumpiness: %.0f%%", settings.TreeClumpiness)) treeClumpinessSlider := newNumericInputSlider(0, 100, settings.TreeClumpiness, "%.0f%%", "Tree Clumpiness")
treeClumpinessSlider := widget.NewSlider(0, 100) treeClumpinessSlider.entry.OnChanged = func(s string) {
treeClumpinessSlider.OnChanged = func(val float64) { treeClumpinessSlider.validate(s, func(hasError bool) {
errorStates["treeClumpiness"] = hasError
updateGenerateBtnState()
})
}
treeClumpinessSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := treeClumpinessSlider.value.Get()
settings.TreeClumpiness = val settings.TreeClumpiness = val
treeClumpinessLabel.SetText(fmt.Sprintf("Tree Clumpiness: %.0f%%", settings.TreeClumpiness)) }))
numRoadsSlider := newNumericInputSlider(0, 2000, float64(settings.NumRoads), "%.0f", "Number of Roads")
numRoadsSlider.entry.OnChanged = func(s string) {
numRoadsSlider.validate(s, func(hasError bool) {
errorStates["numRoads"] = hasError
updateGenerateBtnState()
})
} }
treeClumpinessSlider.SetValue(settings.TreeClumpiness) numRoadsSlider.value.AddListener(binding.NewDataListener(func() {
// Create UI elements for controlling road generation settings val, _ := numRoadsSlider.value.Get()
numRoadsLabel := widget.NewLabel(fmt.Sprintf("Number of Roads: %d", settings.NumRoads))
numRoadsSlider := widget.NewSlider(0, 2000)
numRoadsSlider.OnChanged = func(val float64) {
settings.NumRoads = int(val) settings.NumRoads = int(val)
numRoadsLabel.SetText(fmt.Sprintf("Number of Roads: %d", settings.NumRoads)) }))
minRoadWidthSlider := newNumericInputSlider(1, 150, settings.MinRoadWidth, "%.0fpx", "Min Road Width")
minRoadWidthSlider.entry.OnChanged = func(s string) {
minRoadWidthSlider.validate(s, func(hasError bool) {
errorStates["minRoadWidth"] = hasError
updateGenerateBtnState()
})
} }
numRoadsSlider.SetValue(float64(settings.NumRoads)) minRoadWidthSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := minRoadWidthSlider.value.Get()
minRoadWidthLabel := widget.NewLabel(fmt.Sprintf("Min Road Width: %.0fpx", settings.MinRoadWidth))
minRoadWidthSlider := widget.NewSlider(1, 150)
maxRoadWidthLabel := widget.NewLabel(fmt.Sprintf("Max Road Width: %.0fpx", settings.MaxRoadWidth))
maxRoadWidthSlider := widget.NewSlider(1, 150)
minRoadWidthSlider.OnChanged = func(val float64) {
settings.MinRoadWidth = val settings.MinRoadWidth = val
if settings.MinRoadWidth > settings.MaxRoadWidth { }))
settings.MaxRoadWidth = settings.MinRoadWidth
maxRoadWidthSlider.SetValue(settings.MaxRoadWidth)
}
minRoadWidthLabel.SetText(fmt.Sprintf("Min Road Width: %.0fpx", settings.MinRoadWidth))
}
minRoadWidthSlider.SetValue(settings.MinRoadWidth)
maxRoadWidthSlider.OnChanged = func(val float64) { maxRoadWidthSlider := newNumericInputSlider(1, 150, settings.MaxRoadWidth, "%.0fpx", "Max Road Width")
maxRoadWidthSlider.entry.OnChanged = func(s string) {
maxRoadWidthSlider.validate(s, func(hasError bool) {
errorStates["maxRoadWidth"] = hasError
updateGenerateBtnState()
})
}
maxRoadWidthSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := maxRoadWidthSlider.value.Get()
settings.MaxRoadWidth = val settings.MaxRoadWidth = val
if settings.MaxRoadWidth < settings.MinRoadWidth { }))
settings.MinRoadWidth = settings.MaxRoadWidth
minRoadWidthSlider.SetValue(settings.MinRoadWidth)
}
maxRoadWidthLabel.SetText(fmt.Sprintf("Max Road Width: %.0fpx", settings.MaxRoadWidth))
}
maxRoadWidthSlider.SetValue(settings.MaxRoadWidth)
roadExitsLabel := widget.NewLabel(fmt.Sprintf("Road Exits: %d", settings.RoadExits)) roadExitsSlider := newNumericInputSlider(0, 100, float64(settings.RoadExits), "%.0f", "Road Exits")
roadExitsSlider := widget.NewSlider(0, 100) roadExitsSlider.entry.OnChanged = func(s string) {
roadExitsSlider.OnChanged = func(val float64) { roadExitsSlider.validate(s, func(hasError bool) {
if val > float64(settings.NumRoads) { errorStates["roadExits"] = hasError
val = float64(settings.NumRoads) updateGenerateBtnState()
roadExitsSlider.SetValue(val) })
} }
roadExitsSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := roadExitsSlider.value.Get()
settings.RoadExits = int(val) settings.RoadExits = int(val)
roadExitsLabel.SetText(fmt.Sprintf("Road Exits: %d", settings.RoadExits)) }))
}
roadExitsSlider.SetValue(float64(settings.RoadExits))
roadCurvynessLabel := widget.NewLabel(fmt.Sprintf("Road Curvyness: %.0f%%", settings.RoadCurvyness)) roadCurvynessSlider := newNumericInputSlider(0, 100, settings.RoadCurvyness, "%.0f%%", "Road Curvyness")
roadCurvynessSlider := widget.NewSlider(0, 100) roadCurvynessSlider.entry.OnChanged = func(s string) {
roadCurvynessSlider.OnChanged = func(val float64) { roadCurvynessSlider.validate(s, func(hasError bool) {
errorStates["roadCurvyness"] = hasError
updateGenerateBtnState()
})
}
roadCurvynessSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := roadCurvynessSlider.value.Get()
settings.RoadCurvyness = val settings.RoadCurvyness = val
roadCurvynessLabel.SetText(fmt.Sprintf("Road Curvyness: %.0f%%", settings.RoadCurvyness)) }))
}
roadCurvynessSlider.SetValue(settings.RoadCurvyness)
roadDistributionLabel := widget.NewLabel(fmt.Sprintf("Distribution: %.0f%%", settings.RoadDistribution)) roadDistributionSlider := newNumericInputSlider(0, 100, settings.RoadDistribution, "%.0f%%", "Distribution")
roadDistributionSlider := widget.NewSlider(0, 100) roadDistributionSlider.entry.OnChanged = func(s string) {
roadDistributionSlider.OnChanged = func(val float64) { roadDistributionSlider.validate(s, func(hasError bool) {
settings.RoadDistribution = val errorStates["roadDistribution"] = hasError
roadDistributionLabel.SetText(fmt.Sprintf("Distribution: %.0f%%", settings.RoadDistribution)) updateGenerateBtnState()
})
} }
roadDistributionSlider.SetValue(settings.RoadDistribution) roadDistributionSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := roadDistributionSlider.value.Get()
settings.RoadDistribution = val
}))
// Create UI elements for error display and action buttons // Create UI elements for error display and action buttons
errorLabel := canvas.NewText("", color.RGBA{R: 255, A: 255}) errorLabel := widget.NewLabel("")
errorLabel.TextSize = 12 errorLabel.Wrapping = fyne.TextWrapWord
errorLabel.Hide() errorLabel.Hide()
var generateBtn *widget.Button progressBar := widget.NewProgressBar()
progressBar := NewTextOverlayProgressBar()
exportCanvasBtn := widget.NewButton("Export Canvas", func() { exportCanvasBtn := widget.NewButton("Export Canvas", func() {
showSaveDialog(w, canvasImg.Image, settings) showSaveDialog(w, canvasImg.Image, settings)
}) })
@@ -436,34 +488,17 @@ func main() {
generateBtn.Enable() generateBtn.Enable()
}) })
}() }()
// Set up progress bar
steps := 10
currentStep := 0
seedProvider := NewSeedProvider(settings.Seed) seedProvider := NewSeedProvider(settings.Seed)
// Step 1: Generating Heightmap // Step 1: Generating Heightmap
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Heightmap", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, seedProvider.Next()) noiseImg := GenerateHeightmap(settings.Width, settings.Height, int(settings.Detail), 100.0, seedProvider.Next())
// Step 2: Generating Lakes // Step 2: Generating Lakes
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Lakes", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
var lakeImage image.Image var lakeImage image.Image
lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, seedProvider.Next()) lakeImage, lakes = GenerateLakes(settings.Width, settings.Height, settings.Lakes, settings.LakeSizeLower, settings.LakeSizeUpper, noiseImg, seedProvider.Next())
// Step 3: Generating Rivers // Step 3: Generating Rivers
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Rivers", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
var riverImage image.Image var riverImage image.Image
riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg) riverImage, riverPixels = GenerateRivers(settings.Width, settings.Height, settings.Rivers, settings.MinRiverWidth, settings.MaxRiverWidth, settings.RiverCurvyness, lakeImage, lakes, seedProvider.Next(), noiseImg)
@@ -476,11 +511,7 @@ func main() {
allWaterPixels := append(flatLakePixels, riverPixels...) allWaterPixels := append(flatLakePixels, riverPixels...)
// Step 4: Generating Roads // Step 4: Generating Roads
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Roads", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
var roadImage *image.RGBA var roadImage *image.RGBA
roadPixels, bridgePixels, roadImage = GenerateRoads(settings.Width, settings.Height, settings, noiseImg, allWaterPixels, seedProvider.Next()) // Draw roadImage over finalImage roadPixels, bridgePixels, roadImage = GenerateRoads(settings.Width, settings.Height, settings, noiseImg, allWaterPixels, seedProvider.Next()) // Draw roadImage over finalImage
for y := 0; y < roadImage.Bounds().Max.Y; y++ { for y := 0; y < roadImage.Bounds().Max.Y; y++ {
@@ -493,68 +524,40 @@ func main() {
} }
// Step 5: Generating Buildings // Step 5: Generating Buildings
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Buildings", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
buildings, buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next()) buildings, buildingPixels = GenerateBuildings(finalImage, settings.Width, settings.Height, settings, roadPixels, allWaterPixels, seedProvider.Next())
// Step 6: Darkening Water Areas // Step 6: Darkening Water Areas
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Darkening Water Areas", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels) darkenedHeightmap := DarkenLakeAreas(noiseImg, allWaterPixels)
// Step 7: Flattening Building Areas // Step 7: Flattening Building Areas
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Flattening Building Areas", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height) flattenedBuildingHeightmap := FlattenBuildingAreas(darkenedHeightmap.(*image.RGBA), buildings, settings.Width, settings.Height)
flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadPixels) flattenedHeightmap := FlattenRoadAreas(flattenedBuildingHeightmap, roadPixels)
// Step 8: Applying Roughness // Step 8: Applying Roughness
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Applying Roughness", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness) compositeImg := ApplyRoughness(flattenedHeightmap, settings.Roughness)
// Step 9: Generating Trees // Step 9: Generating Trees
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Trees", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next()) treePixels = GenerateTrees(finalImage, allWaterPixels, roadPixels, buildingPixels, settings.MinTreeSize, settings.MaxTreeSize, settings.TreeCoverage, settings.TreeClumpiness, seedProvider.Next())
// Step 10: Generating Bump Map // Step 10: Generating Bump Map
currentStep++
fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Generating Bump Map", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
})
bumpMap := GenerateBumpMap(compositeImg.(*image.RGBA), settings.Width, settings.Height, 0.10) bumpMap := GenerateBumpMap(compositeImg.(*image.RGBA), settings.Width, settings.Height, 0.10)
// Step 11: Finalizing Images // Step 11: Finalizing Images
currentStep++
fyne.Do(func() { fyne.Do(func() {
progressBar.SetText(fmt.Sprintf("Step %d/%d: Finalizing Images", currentStep, steps))
progressBar.SetValue(float64(currentStep) / float64(steps))
heightmapImg.Image = compositeImg heightmapImg.Image = compositeImg
heightmapImg.Refresh() heightmapImg.Refresh()
bumpmapImg.Image = bumpMap bumpmapImg.Image = bumpMap
bumpmapImg.Refresh() bumpmapImg.Refresh()
canvasImg.Image = finalImage canvasImg.Image = finalImage
canvasImg.Refresh() canvasImg.Refresh()
progressBar.SetText("Generation Complete!")
}) })
}() }()
}) })
@@ -609,100 +612,85 @@ func main() {
}) })
// Create tabs for organizing settings // Create tabs for organizing settings
terrainTab := container.NewTabItem("Terrain", container.NewVBox( terrainTab := container.NewTabItem("Terrain", container.NewVBox(
detailLabel,
detailSlider, detailSlider,
roughnessLabel,
roughnessSlider, roughnessSlider,
widget.NewLabel(""), // Spacer widget.NewLabel(""), // Spacer
minTreeSizeLabel,
minTreeSizeSlider, minTreeSizeSlider,
maxTreeSizeLabel,
maxTreeSizeSlider, maxTreeSizeSlider,
treeCoverageLabel,
treeCoverageSlider, treeCoverageSlider,
treeClumpinessLabel,
treeClumpinessSlider, treeClumpinessSlider,
)) ))
waterTab := container.NewTabItem("Water", container.NewVBox( waterTab := container.NewTabItem("Water", container.NewVBox(
lakesLabel,
lakesSlider, lakesSlider,
lakeSizeLowerLabel,
lakeSizeLowerSlider, lakeSizeLowerSlider,
lakeSizeUpperLabel,
lakeSizeUpperSlider, lakeSizeUpperSlider,
widget.NewLabel(""), // Spacer widget.NewLabel(""), // Spacer
riversLabel,
riversSlider, riversSlider,
minRiverWidthLabel,
minRiverWidthSlider, minRiverWidthSlider,
maxRiverWidthLabel,
maxRiverWidthSlider, maxRiverWidthSlider,
riverCurvynessLabel,
riverCurvynessSlider, riverCurvynessSlider,
)) ))
roadDistributionSlider.SetValue(settings.RoadDistribution)
roadsTab := container.NewTabItem("Roads", container.NewVBox( roadsTab := container.NewTabItem("Roads", container.NewVBox(
numRoadsLabel,
numRoadsSlider, numRoadsSlider,
minRoadWidthLabel,
minRoadWidthSlider, minRoadWidthSlider,
maxRoadWidthLabel,
maxRoadWidthSlider, maxRoadWidthSlider,
roadExitsLabel,
roadExitsSlider, roadExitsSlider,
roadCurvynessLabel,
roadCurvynessSlider, roadCurvynessSlider,
roadDistributionLabel,
roadDistributionSlider, roadDistributionSlider,
)) ))
// Create UI elements for building generation settings numBuildingsSlider := newNumericInputSlider(0, 10000, float64(settings.NumBuildings), "%.0f", "Number of Buildings")
numBuildingsLabel := widget.NewLabel(fmt.Sprintf("Number of Buildings: %d", settings.NumBuildings)) numBuildingsSlider.entry.OnChanged = func(s string) {
numBuildingsSlider := widget.NewSlider(0, 10000) numBuildingsSlider.validate(s, func(hasError bool) {
numBuildingsSlider.OnChanged = func(val float64) { errorStates["numBuildings"] = hasError
updateGenerateBtnState()
})
}
numBuildingsSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := numBuildingsSlider.value.Get()
settings.NumBuildings = int(val) settings.NumBuildings = int(val)
numBuildingsLabel.SetText(fmt.Sprintf("Number of Buildings: %d", settings.NumBuildings)) }))
minBuildingSizeSlider := newNumericInputSlider(1, 150, settings.MinBuildingSize, "%.0fpx", "Min Building Size")
minBuildingSizeSlider.entry.OnChanged = func(s string) {
minBuildingSizeSlider.validate(s, func(hasError bool) {
errorStates["minBuildingSize"] = hasError
updateGenerateBtnState()
})
} }
numBuildingsSlider.SetValue(float64(settings.NumBuildings)) minBuildingSizeSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := minBuildingSizeSlider.value.Get()
minBuildingSizeLabel := widget.NewLabel(fmt.Sprintf("Min Building Size: %.0fpx", settings.MinBuildingSize))
minBuildingSizeSlider := widget.NewSlider(1, 150)
maxBuildingSizeLabel := widget.NewLabel(fmt.Sprintf("Max Building Size: %.0fpx", settings.MaxBuildingSize))
maxBuildingSizeSlider := widget.NewSlider(1, 150)
minBuildingSizeSlider.OnChanged = func(val float64) {
settings.MinBuildingSize = val settings.MinBuildingSize = val
if settings.MinBuildingSize > settings.MaxBuildingSize { }))
settings.MaxBuildingSize = settings.MinBuildingSize
maxBuildingSizeSlider.SetValue(settings.MaxBuildingSize)
}
minBuildingSizeLabel.SetText(fmt.Sprintf("Min Building Size: %.0fpx", settings.MinBuildingSize))
}
minBuildingSizeSlider.SetValue(settings.MinBuildingSize)
maxBuildingSizeSlider.OnChanged = func(val float64) { maxBuildingSizeSlider := newNumericInputSlider(1, 150, settings.MaxBuildingSize, "%.0fpx", "Max Building Size")
maxBuildingSizeSlider.entry.OnChanged = func(s string) {
maxBuildingSizeSlider.validate(s, func(hasError bool) {
errorStates["maxBuildingSize"] = hasError
updateGenerateBtnState()
})
}
maxBuildingSizeSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := maxBuildingSizeSlider.value.Get()
settings.MaxBuildingSize = val settings.MaxBuildingSize = val
if settings.MaxBuildingSize < settings.MinBuildingSize { }))
settings.MinBuildingSize = settings.MaxBuildingSize
minBuildingSizeSlider.SetValue(settings.MinBuildingSize)
}
maxBuildingSizeLabel.SetText(fmt.Sprintf("Max Building Size: %.0fpx", settings.MaxBuildingSize))
}
maxBuildingSizeSlider.SetValue(settings.MaxBuildingSize)
buildingDistributionLabel := widget.NewLabel(fmt.Sprintf("Building Distribution: %.0f%%", settings.BuildingDistribution)) buildingDistributionSlider := newNumericInputSlider(0, 100, settings.BuildingDistribution, "%.0f%%", "Building Distribution")
buildingDistributionSlider := widget.NewSlider(0, 100) buildingDistributionSlider.entry.OnChanged = func(s string) {
buildingDistributionSlider.OnChanged = func(val float64) { buildingDistributionSlider.validate(s, func(hasError bool) {
settings.BuildingDistribution = val errorStates["buildingDistribution"] = hasError
buildingDistributionLabel.SetText(fmt.Sprintf("Building Distribution: %.0f%%", settings.BuildingDistribution)) updateGenerateBtnState()
})
} }
buildingDistributionSlider.SetValue(settings.BuildingDistribution) buildingDistributionSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := buildingDistributionSlider.value.Get()
settings.BuildingDistribution = val
}))
buildingShapeLabel := widget.NewLabel("Building Shape:") buildingShapeLabel := widget.NewLabel("Building Shape:")
buildingShapeSelect := widget.NewSelect([]string{"squares", "circles", "rectangles", "mixed", "procedural"}, func(s string) { buildingShapeSelect := widget.NewSelect([]string{"squares", "circles", "rectangles", "mixed", "procedural"}, func(s string) {
@@ -732,43 +720,45 @@ func main() {
rectangleRatioSlider, rectangleRatioSlider,
) )
// Create sliders for procedural building complexity minBuildingComplexitySlider := newNumericInputSlider(1, 6, float64(settings.MinBuildingComplexity), "%.0f", "Min Building Complexity")
minBuildingComplexityLabel := widget.NewLabel(fmt.Sprintf("Min Building Complexity: %d", settings.MinBuildingComplexity)) minBuildingComplexitySlider.entry.OnChanged = func(s string) {
minBuildingComplexitySlider := widget.NewSlider(1, 6) minBuildingComplexitySlider.validate(s, func(hasError bool) {
minBuildingComplexitySlider.OnChanged = func(val float64) { errorStates["minBuildingComplexity"] = hasError
updateGenerateBtnState()
})
}
minBuildingComplexitySlider.value.AddListener(binding.NewDataListener(func() {
val, _ := minBuildingComplexitySlider.value.Get()
settings.MinBuildingComplexity = int(val) settings.MinBuildingComplexity = int(val)
if float64(settings.MinBuildingComplexity) > float64(settings.MaxBuildingComplexity) { }))
settings.MaxBuildingComplexity = settings.MinBuildingComplexity
}
minBuildingComplexityLabel.SetText(fmt.Sprintf("Min Building Complexity: %d", settings.MinBuildingComplexity))
}
minBuildingComplexitySlider.SetValue(float64(settings.MinBuildingComplexity))
maxBuildingComplexityLabel := widget.NewLabel(fmt.Sprintf("Max Building Complexity: %d", settings.MaxBuildingComplexity)) maxBuildingComplexitySlider := newNumericInputSlider(1, 6, float64(settings.MaxBuildingComplexity), "%.0f", "Max Building Complexity")
maxBuildingComplexitySlider := widget.NewSlider(1, 6) maxBuildingComplexitySlider.entry.OnChanged = func(s string) {
maxBuildingComplexitySlider.OnChanged = func(val float64) { maxBuildingComplexitySlider.validate(s, func(hasError bool) {
errorStates["maxBuildingComplexity"] = hasError
updateGenerateBtnState()
})
}
maxBuildingComplexitySlider.value.AddListener(binding.NewDataListener(func() {
val, _ := maxBuildingComplexitySlider.value.Get()
settings.MaxBuildingComplexity = int(val) settings.MaxBuildingComplexity = int(val)
if float64(settings.MaxBuildingComplexity) < float64(settings.MinBuildingComplexity) { }))
settings.MinBuildingComplexity = settings.MaxBuildingComplexity
}
maxBuildingComplexityLabel.SetText(fmt.Sprintf("Max Building Complexity: %d", settings.MaxBuildingComplexity))
}
maxBuildingComplexitySlider.SetValue(float64(settings.MaxBuildingComplexity))
buildingComplexityRatioLabel := widget.NewLabel(fmt.Sprintf("Building Complexity Ratio: %.0f%%", settings.BuildingComplexityRatio)) buildingComplexityRatioSlider := newNumericInputSlider(0, 100, settings.BuildingComplexityRatio, "%.0f%%", "Building Complexity Ratio")
buildingComplexityRatioSlider := widget.NewSlider(0, 100) buildingComplexityRatioSlider.entry.OnChanged = func(s string) {
buildingComplexityRatioSlider.OnChanged = func(val float64) { buildingComplexityRatioSlider.validate(s, func(hasError bool) {
settings.BuildingComplexityRatio = val errorStates["buildingComplexityRatio"] = hasError
buildingComplexityRatioLabel.SetText(fmt.Sprintf("Building Complexity Ratio: %.0f%%", settings.BuildingComplexityRatio)) updateGenerateBtnState()
})
} }
buildingComplexityRatioSlider.SetValue(settings.BuildingComplexityRatio) buildingComplexityRatioSlider.value.AddListener(binding.NewDataListener(func() {
val, _ := buildingComplexityRatioSlider.value.Get()
settings.BuildingComplexityRatio = val
}))
proceduralContainer := container.NewVBox( proceduralContainer := container.NewVBox(
minBuildingComplexityLabel,
minBuildingComplexitySlider, minBuildingComplexitySlider,
maxBuildingComplexityLabel,
maxBuildingComplexitySlider, maxBuildingComplexitySlider,
buildingComplexityRatioLabel,
buildingComplexityRatioSlider, buildingComplexityRatioSlider,
) )
@@ -899,13 +889,9 @@ func main() {
} }
buildingsTab := container.NewTabItem("Buildings", container.NewVBox( buildingsTab := container.NewTabItem("Buildings", container.NewVBox(
numBuildingsLabel,
numBuildingsSlider, numBuildingsSlider,
minBuildingSizeLabel,
minBuildingSizeSlider, minBuildingSizeSlider,
maxBuildingSizeLabel,
maxBuildingSizeSlider, maxBuildingSizeSlider,
buildingDistributionLabel,
buildingDistributionSlider, buildingDistributionSlider,
buildingShapeLabel, buildingShapeLabel,
buildingShapeSelect, buildingShapeSelect,