Files
RPG_City_Maker_Reborn/custom_widgets.go
T

183 lines
4.2 KiB
Go
Raw Normal View History

2026-01-28 12:35:10 -06:00
package main
import (
2026-02-12 14:33:47 -06:00
"fmt"
"math"
2026-02-12 14:33:47 -06:00
"strconv"
"strings"
2026-01-28 12:35:10 -06:00
"fyne.io/fyne/v2"
2026-02-12 14:33:47 -06:00
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/layout"
2026-01-28 12:35:10 -06:00
"fyne.io/fyne/v2/widget"
)
2026-02-19 09:49:26 -06:00
type tappableImage struct {
widget.BaseWidget
image *fyne.Container
onTapped func()
}
func newTappableImage(img *fyne.Container, tapped func()) *tappableImage {
ti := &tappableImage{
image: img,
onTapped: tapped,
}
ti.ExtendBaseWidget(ti)
return ti
}
func (t *tappableImage) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(t.image)
}
func (t *tappableImage) Tapped(*fyne.PointEvent) {
if t.onTapped != nil {
t.onTapped()
}
}
2026-02-18 15:26:29 -06:00
// numericInputSlider combines a slider and text entry for numeric input
2026-02-12 14:33:47 -06:00
type numericInputSlider struct {
2026-01-28 12:35:10 -06:00
widget.BaseWidget
2026-02-12 14:33:47 -06:00
value binding.Float
min, max float64
step float64
2026-02-12 14:33:47 -06:00
slider *widget.Slider
entry *widget.Entry
format string
errorLabel *widget.Label
label *widget.Label
2026-01-28 12:35:10 -06:00
}
2026-02-12 14:33:47 -06:00
type numericInputSliderRenderer struct {
slider *numericInputSlider
label *widget.Label
entry *widget.Entry
sliderWidget *widget.Slider
errorLabel *widget.Label
layout fyne.Layout
objects []fyne.CanvasObject
2026-01-28 12:35:10 -06:00
}
2026-02-12 14:33:47 -06:00
func (r *numericInputSliderRenderer) MinSize() fyne.Size {
return r.layout.MinSize(r.objects)
2026-01-28 12:35:10 -06:00
}
2026-02-12 14:33:47 -06:00
func (r *numericInputSliderRenderer) Layout(size fyne.Size) {
r.layout.Layout(r.objects, size)
2026-01-28 12:35:10 -06:00
}
2026-02-12 14:33:47 -06:00
func (r *numericInputSliderRenderer) Objects() []fyne.CanvasObject {
2026-01-28 12:35:10 -06:00
return r.objects
}
2026-02-12 14:33:47 -06:00
func (r *numericInputSliderRenderer) Refresh() {
r.label.SetText(r.slider.label.Text)
2026-01-28 12:35:10 -06:00
}
2026-02-12 14:33:47 -06:00
func (r *numericInputSliderRenderer) Destroy() {}
2026-01-28 12:35:10 -06:00
2026-02-18 15:26:29 -06:00
// newNumericInputSlider creates a new numericInputSlider widget
2026-02-12 14:33:47 -06:00
func newNumericInputSlider(min, max float64, initialValue float64, format string, labelText string) *numericInputSlider {
s := &numericInputSlider{
min: min,
max: max,
format: format,
step: 0,
2026-01-28 12:35:10 -06:00
}
2026-02-12 14:33:47 -06:00
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
}
func newNumericInputSliderWithStep(min, max, initialValue, step float64, format string, labelText string) *numericInputSlider {
s := newNumericInputSlider(min, max, initialValue, format, labelText)
if step > 0 {
s.step = step
s.slider.Step = step
rounded := min + math.Round((initialValue-min)/step)*step
s.value.Set(rounded)
}
return s
}
2026-02-18 15:26:29 -06:00
// validate checks text entry for valid numeric input within the defined range
2026-02-12 14:33:47 -06:00
func (s *numericInputSlider) validate(text string, onError func(bool)) {
text = strings.TrimSpace(text)
2026-02-12 14:33:47 -06:00
text = strings.TrimSuffix(text, "px")
text = strings.TrimSuffix(text, "%")
text = strings.TrimSuffix(text, "°")
text = strings.TrimSpace(text)
2026-02-12 14:33:47 -06:00
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 (%s-%s)",
strconv.FormatFloat(s.min, 'f', -1, 64),
strconv.FormatFloat(s.max, 'f', -1, 64),
))
2026-02-12 14:33:47 -06:00
s.errorLabel.Show()
onError(true)
return
}
if s.step > 0 {
steps := math.Round((val - s.min) / s.step)
snapped := s.min + steps*s.step
if math.Abs(val-snapped) > 1e-9 {
s.errorLabel.SetText(fmt.Sprintf("Use increments of %s", strconv.FormatFloat(s.step, 'f', -1, 64)))
s.errorLabel.Show()
onError(true)
return
}
val = snapped
}
2026-02-12 14:33:47 -06:00
s.errorLabel.Hide()
onError(false)
s.value.Set(val)
}
2026-02-18 15:26:29 -06:00
// CreateRenderer renders the widget as required by Fyne toolkit
2026-02-12 14:33:47 -06:00
func (s *numericInputSlider) CreateRenderer() fyne.WidgetRenderer {
r := &numericInputSliderRenderer{
slider: s,
label: s.label,
entry: s.entry,
sliderWidget: s.slider,
errorLabel: s.errorLabel,
}
2026-02-13 09:29:06 -06:00
r.layout = layout.NewGridLayout(1)
2026-02-12 14:33:47 -06:00
r.objects = []fyne.CanvasObject{
2026-02-13 09:29:06 -06:00
container.NewGridWithColumns(2, r.label, r.entry),
2026-02-12 14:33:47 -06:00
r.sliderWidget,
r.errorLabel,
}
return r
2026-01-28 12:35:10 -06:00
}