ui calculator
This commit is contained in:
+7
-19
@@ -1,22 +1,10 @@
|
||||
# Client for Galaxy Plus
|
||||
|
||||
## Ship Calculator
|
||||
UI Client is capable of:
|
||||
|
||||
```text
|
||||
Class: [ ] { Create }
|
||||
Drives: [20.000] x 1.013 [O--]
|
||||
Weapons: [ 0.000] x [1.000] [==0]
|
||||
Armament: [ 0 ]
|
||||
Schields: [ 5.500] @ [1.123]
|
||||
Cargo: [30.125] @ [1.320]
|
||||
|
||||
Mass: [ 123,45 ] [==0]
|
||||
Speed: ( 12,456 ) [O--]
|
||||
Attack: 0
|
||||
Defense: 100,0
|
||||
|
||||
Planet { Name } Production:
|
||||
[ 100.0 ] MAT per turn produced [O--] supplied
|
||||
{ N.000 } ship(s) per turn
|
||||
{ M.000 } turn(s) per ship
|
||||
```
|
||||
- Register a new player and login for an existing player using only e-mail and one-time codes,
|
||||
- Enlist to a new Game from available onboard Games list,
|
||||
- Request list of Games in which Player participating,
|
||||
- Request, store and display particular Game data,
|
||||
- Use push-like mechanism for receiving asynchronous updates from Server,
|
||||
- Offline mode when no internet connection is available or user desired to work offline.
|
||||
|
||||
+50
-3
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
gerr "galaxy/error"
|
||||
@@ -9,10 +10,11 @@ import (
|
||||
var (
|
||||
checkConnectionInterval = 5 * time.Second
|
||||
checkVersionInterval = time.Hour
|
||||
statePersistInterval = time.Second
|
||||
)
|
||||
|
||||
func (e *client) startBackground() {
|
||||
if e.fullConnector == nil && e.updater == nil {
|
||||
if e.conn == nil || e.updater == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,9 +30,11 @@ func (e *client) stopBackground() {
|
||||
func (e *client) backgroundLoop() {
|
||||
checkConnTimer := time.NewTimer(checkConnectionInterval)
|
||||
checkVersionTimer := time.NewTimer(checkVersionInterval)
|
||||
persistStateTimer := time.NewTimer(statePersistInterval)
|
||||
defer func() {
|
||||
checkConnTimer.Stop()
|
||||
checkVersionTimer.Stop()
|
||||
persistStateTimer.Stop()
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -38,8 +42,8 @@ func (e *client) backgroundLoop() {
|
||||
case <-e.backgroundStop:
|
||||
return
|
||||
case <-checkConnTimer.C:
|
||||
if e.fullConnector != nil {
|
||||
e.OnConnection(e.fullConnector.CheckConnection())
|
||||
if e.conn != nil {
|
||||
e.OnConnection(e.conn.CheckConnection())
|
||||
}
|
||||
checkConnTimer.Reset(checkConnectionInterval)
|
||||
case <-checkVersionTimer.C:
|
||||
@@ -49,15 +53,58 @@ func (e *client) backgroundLoop() {
|
||||
}
|
||||
}
|
||||
checkVersionTimer.Reset(checkVersionInterval)
|
||||
case <-persistStateTimer.C:
|
||||
e.ensureStatePersist()
|
||||
persistStateTimer.Reset(statePersistInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *client) ensureStatePersist() {
|
||||
param := e.GetParams()
|
||||
needSaving := false
|
||||
e.stateMu.Lock()
|
||||
if e.world != nil {
|
||||
if param.CameraZoom > 0 && param.CameraZoom != e.state.CameraZoom {
|
||||
e.state.CameraZoom = param.CameraZoom
|
||||
needSaving = true
|
||||
}
|
||||
if param.CameraXWorldFp != e.state.CameraXFp {
|
||||
e.state.CameraXFp = param.CameraXWorldFp
|
||||
needSaving = true
|
||||
}
|
||||
if param.CameraYWorldFp != e.state.CameraYFp {
|
||||
e.state.CameraYFp = param.CameraYWorldFp
|
||||
needSaving = true
|
||||
}
|
||||
}
|
||||
if e.mapSplitter != nil && e.mapSplitter.Offset != e.state.MapSplitterOffset {
|
||||
e.state.MapSplitterOffset = e.mapSplitter.Offset
|
||||
needSaving = true
|
||||
}
|
||||
if e.accInfo.Open != e.state.AccordionInfoOpen {
|
||||
e.state.AccordionInfoOpen = e.accInfo.Open
|
||||
needSaving = true
|
||||
}
|
||||
if e.accCalc.Open != e.state.AccordionCalcOpen {
|
||||
e.state.AccordionCalcOpen = e.accCalc.Open
|
||||
needSaving = true
|
||||
}
|
||||
if needSaving {
|
||||
if err := e.s.SaveState(*e.state); err != nil {
|
||||
e.handlerError(err)
|
||||
}
|
||||
}
|
||||
e.stateMu.Unlock()
|
||||
}
|
||||
|
||||
func (e *client) handlerError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("ERROR: %s\n", err)
|
||||
|
||||
switch {
|
||||
case gerr.IsConnection(err):
|
||||
e.OnConnectionError(err)
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
type interactiveRaster struct {
|
||||
widget.BaseWidget
|
||||
|
||||
edit *client
|
||||
min fyne.Size
|
||||
raster *canvas.Raster
|
||||
onLayout func(fyne.Size)
|
||||
@@ -50,7 +49,6 @@ func (r *interactiveRaster) Tapped(ev *fyne.PointEvent) {
|
||||
func (r *interactiveRaster) TappedSecondary(*fyne.PointEvent) {}
|
||||
|
||||
func newInteractiveRaster(
|
||||
edit *client,
|
||||
raster *canvas.Raster,
|
||||
onLayout func(fyne.Size),
|
||||
onScrolled func(*fyne.ScrollEvent),
|
||||
@@ -60,7 +58,6 @@ func newInteractiveRaster(
|
||||
) *interactiveRaster {
|
||||
r := &interactiveRaster{
|
||||
raster: raster,
|
||||
edit: edit,
|
||||
onLayout: onLayout,
|
||||
onScrolled: onScrolled,
|
||||
onDragged: onDragged,
|
||||
|
||||
+100
-63
@@ -5,15 +5,16 @@ import (
|
||||
"sync"
|
||||
|
||||
"galaxy/client/updater"
|
||||
"galaxy/client/widget/calculator"
|
||||
"galaxy/client/world"
|
||||
"galaxy/connector"
|
||||
mc "galaxy/model/client"
|
||||
"galaxy/model/report"
|
||||
"galaxy/storage"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
@@ -21,12 +22,22 @@ import (
|
||||
const version = "1.0.0"
|
||||
|
||||
type client struct {
|
||||
s storage.UIStorage
|
||||
conn connector.UIConnector
|
||||
s storage.Storage
|
||||
conn connector.Connector
|
||||
app fyne.App
|
||||
window fyne.Window
|
||||
|
||||
loadReportFunc func(uint)
|
||||
state *mc.State
|
||||
stateMu sync.RWMutex
|
||||
|
||||
reg *registry
|
||||
|
||||
calculator *calculator.Calculator
|
||||
mapSplitter *container.Split
|
||||
accInfo *widget.AccordionItem
|
||||
accCalc *widget.AccordionItem
|
||||
|
||||
// loadReportFunc func(uint)
|
||||
|
||||
world *world.World
|
||||
drawer *world.GGDrawer
|
||||
@@ -69,8 +80,6 @@ type client struct {
|
||||
|
||||
hits []world.Hit
|
||||
|
||||
fullStorage storage.Storage
|
||||
fullConnector connector.Connector
|
||||
updater *updater.Manager
|
||||
backgroundStop chan struct{}
|
||||
backgroundOnce sync.Once
|
||||
@@ -81,32 +90,55 @@ type client struct {
|
||||
onServiceErrFn func(error)
|
||||
}
|
||||
|
||||
func NewClient(s storage.UIStorage, conn connector.UIConnector, app fyne.App) (mc.Client, error) {
|
||||
func NewClient(s storage.Storage, conn connector.Connector, app fyne.App) (mc.Client, error) {
|
||||
e := &client{
|
||||
s: s,
|
||||
conn: conn,
|
||||
app: app,
|
||||
window: app.NewWindow("Galaxy Plus"),
|
||||
world: nil,
|
||||
wp: &world.RenderParams{
|
||||
CameraZoom: 1.0,
|
||||
Options: &world.RenderOptions{DisableWrapScroll: false},
|
||||
},
|
||||
s: s,
|
||||
conn: conn,
|
||||
app: app,
|
||||
window: app.NewWindow("Galaxy Plus"),
|
||||
reg: newRegistry(),
|
||||
lastCanvasScale: 1.0,
|
||||
world: nil,
|
||||
hits: make([]world.Hit, 5),
|
||||
backgroundStop: make(chan struct{}),
|
||||
}
|
||||
if fullStorage, ok := s.(storage.Storage); ok {
|
||||
e.fullStorage = fullStorage
|
||||
}
|
||||
if fullConnector, ok := conn.(connector.Connector); ok {
|
||||
e.fullConnector = fullConnector
|
||||
}
|
||||
if e.fullStorage != nil && e.fullConnector != nil {
|
||||
e.updater = updater.NewManager(e.fullStorage, e.fullConnector)
|
||||
}
|
||||
e.calculator = calculator.NewCaclulator(calculator.WithCreateHandler(e.createShipClass))
|
||||
e.updater = updater.NewManager(e.s, e.conn)
|
||||
|
||||
e.loadReportFunc = e.loadReport
|
||||
stateExists, err := e.s.StateExists()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stateExists {
|
||||
state, err := e.s.LoadState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.state = &state
|
||||
} else {
|
||||
e.state = &mc.State{
|
||||
ClientCurrentVersion: e.Version(),
|
||||
CameraZoom: 1.0,
|
||||
MapSplitterOffset: 0.5,
|
||||
AccordionInfoOpen: false,
|
||||
AccordionCalcOpen: false,
|
||||
}
|
||||
if err := e.s.SaveState(*e.state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if e.state.CameraZoom <= 0 {
|
||||
e.state.CameraZoom = 1.0
|
||||
}
|
||||
if e.state.MapSplitterOffset <= 0 {
|
||||
e.state.MapSplitterOffset = 0.5
|
||||
}
|
||||
e.wp = &world.RenderParams{
|
||||
Options: &world.RenderOptions{DisableWrapScroll: false},
|
||||
CameraZoom: e.state.CameraZoom,
|
||||
CameraXWorldFp: e.state.CameraXFp,
|
||||
CameraYWorldFp: e.state.CameraYFp,
|
||||
}
|
||||
|
||||
e.drawer = &world.GGDrawer{DC: nil}
|
||||
|
||||
@@ -127,40 +159,13 @@ func NewClient(s storage.UIStorage, conn connector.UIConnector, app fyne.App) (m
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (e *client) loadReport(t uint) {
|
||||
e.conn.FetchReport("GAME_ID", t, func(r report.Report, err error) {
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
} else {
|
||||
e.setReport(r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *client) setReport(r report.Report) {
|
||||
w := world.NewWorld(int(r.Width), int(r.Height))
|
||||
for i := range r.LocalPlanet {
|
||||
p := r.LocalPlanet[i]
|
||||
w.AddCircle(p.X.F(), p.Y.F(), p.Size.F())
|
||||
}
|
||||
for i := range r.UnidentifiedPlanet {
|
||||
p := r.UnidentifiedPlanet[i]
|
||||
w.AddPoint(p.X.F(), p.Y.F())
|
||||
}
|
||||
e.loadWorld(w)
|
||||
}
|
||||
|
||||
func (e *client) BuildUI(w fyne.Window) {
|
||||
mapCanvas := newInteractiveRaster(e, e.raster, e.onRasterWidgetLayout, e.onScrolled, e.onDragged, e.onDradEnd, e.onTapped)
|
||||
mapCanvas.SetMinSize(fyne.NewSize(640, 480))
|
||||
mapCanvasObject := newInteractiveRaster(e.raster, e.onRasterWidgetLayout, e.onScrolled, e.onDragged, e.onDradEnd, e.onTapped)
|
||||
|
||||
toolbar := widget.NewToolbar(
|
||||
widget.NewToolbarAction(
|
||||
theme.FolderIcon(),
|
||||
func() {
|
||||
e.loadReport(0)
|
||||
// e.loadWorld(mockWorld())
|
||||
}),
|
||||
func() { e.initReportAsync("GAME_ID", 0) }),
|
||||
widget.NewToolbarSeparator(),
|
||||
widget.NewToolbarAction(
|
||||
theme.NavigateBackIcon(),
|
||||
@@ -170,11 +175,24 @@ func (e *client) BuildUI(w fyne.Window) {
|
||||
func() {}),
|
||||
)
|
||||
|
||||
e.accInfo = widget.NewAccordionItem(lang.L("title.info"), container.NewStack())
|
||||
e.accInfo.Open = e.state.AccordionInfoOpen
|
||||
e.accCalc = widget.NewAccordionItem(lang.L("title.calculator"), e.calculator.CanvasObject)
|
||||
e.accCalc.Open = e.state.AccordionCalcOpen
|
||||
|
||||
accordion := widget.NewAccordion()
|
||||
accordion.MultiOpen = true
|
||||
accordion.Append(e.accCalc)
|
||||
accordion.Append(e.accInfo)
|
||||
|
||||
e.mapSplitter = container.NewHSplit(mapCanvasObject, container.NewHScroll(accordion))
|
||||
e.mapSplitter.SetOffset(e.state.MapSplitterOffset)
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItemWithIcon(
|
||||
"Map",
|
||||
lang.L("title.map"),
|
||||
theme.GridIcon(),
|
||||
mapCanvas),
|
||||
e.mapSplitter),
|
||||
container.NewTabItemWithIcon(
|
||||
"Calculator",
|
||||
theme.ComputerIcon(),
|
||||
@@ -182,16 +200,33 @@ func (e *client) BuildUI(w fyne.Window) {
|
||||
),
|
||||
)
|
||||
|
||||
th := tabs.Theme()
|
||||
icon := canvas.NewImageFromResource(th.Icon(theme.IconNameInfo))
|
||||
|
||||
statusLeft := widget.NewTextGridFromString("Status")
|
||||
statusAd := widget.NewTextGridFromString("")
|
||||
|
||||
statusBar := container.NewBorder(
|
||||
nil, // top
|
||||
nil, // bottom
|
||||
container.NewHBox(statusLeft, widget.NewSeparator()), // left
|
||||
container.NewHBox(widget.NewSeparator(), icon), // right
|
||||
statusAd, // center
|
||||
)
|
||||
|
||||
content := container.NewBorder(
|
||||
toolbar, // top
|
||||
nil, // bottom
|
||||
nil, // left
|
||||
nil, // right
|
||||
tabs, // center
|
||||
toolbar, // top
|
||||
statusBar, // bottom
|
||||
nil, // left
|
||||
nil, // right
|
||||
tabs, // center
|
||||
)
|
||||
|
||||
w.CenterOnScreen()
|
||||
w.SetContent(content)
|
||||
s := statusBar.Size()
|
||||
icon.SetMinSize(fyne.NewSize(s.Height, s.Height))
|
||||
e.initLatestReport()
|
||||
}
|
||||
|
||||
func (e *client) loadWorld(w *world.World) {
|
||||
@@ -215,16 +250,18 @@ func (e *client) Run() error {
|
||||
e.window.SetMaster()
|
||||
e.window.Resize(fyne.NewSize(800, 600))
|
||||
e.window.CenterOnScreen()
|
||||
e.window.SetOnClosed(e.Shutdown)
|
||||
e.window.ShowAndRun()
|
||||
e.stopBackground()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *client) Shutdown() {
|
||||
e.stopBackground()
|
||||
e.ensureStatePersist()
|
||||
e.window.Close()
|
||||
}
|
||||
|
||||
// TODO: remove func?
|
||||
func (e *client) Version() string { return version }
|
||||
|
||||
func (e *client) OnConnection(isGood bool) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"galaxy/client"
|
||||
"galaxy/client/appmeta"
|
||||
"galaxy/client/loader"
|
||||
"galaxy/connector/http"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
"os/signal"
|
||||
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -19,7 +21,7 @@ func main() {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Join(err, fmt.Errorf("app panics: %v", r))
|
||||
err = errors.Join(err, fmt.Errorf("panic: %v", r))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -32,6 +34,9 @@ func main() {
|
||||
defer cancel()
|
||||
|
||||
app := app.NewWithID(appmeta.AppID)
|
||||
if err = lang.AddTranslationsFS(client.Translations, "resource/lang"); err != nil {
|
||||
return
|
||||
}
|
||||
s, err := fs.NewFS(app.Storage().RootURI().Path())
|
||||
if err != nil {
|
||||
return
|
||||
@@ -44,6 +49,5 @@ func main() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = l.Run(ctx)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"galaxy/client/appmeta"
|
||||
"galaxy/client"
|
||||
"galaxy/client/appmeta"
|
||||
"galaxy/connector/http"
|
||||
"galaxy/storage/fs"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -19,7 +20,7 @@ func main() {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.Join(err, fmt.Errorf("app panics: %v", r))
|
||||
err = errors.Join(err, fmt.Errorf("panic: %v", r))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -31,6 +32,9 @@ func main() {
|
||||
defer cancel()
|
||||
|
||||
app := app.NewWithID(appmeta.AppID)
|
||||
if err = lang.AddTranslationsFS(client.Translations, "resource/lang"); err != nil {
|
||||
return
|
||||
}
|
||||
s, err := fs.NewFS(app.Storage().RootURI().Path())
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package client
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed resource/lang
|
||||
var Translations embed.FS
|
||||
@@ -0,0 +1,61 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"galaxy/client/world"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
var m = func(v int) float64 { return float64(v) / float64(world.SCALE) }
|
||||
|
||||
func (e *client) onTapped(ev *fyne.PointEvent) {
|
||||
if e.world == nil || ev == nil {
|
||||
return
|
||||
}
|
||||
|
||||
xPx, yPx, ok := e.eventPosToPixel(ev.Position.X, ev.Position.Y)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := e.getLastRenderedParams()
|
||||
hits, err := e.world.HitTest(e.hits, ¶ms, xPx, yPx)
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(hits) == 0 {
|
||||
e.calculator.UnloadPlanet()
|
||||
return
|
||||
}
|
||||
|
||||
for i := range hits {
|
||||
e.onHit(hits[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (e *client) onHit(hit world.Hit) {
|
||||
// var coord string
|
||||
// if hit.Kind == world.KindLine {
|
||||
// coord = fmt.Sprintf("{%f,%f - %f,%f}", m(hit.X1), m(hit.Y1), m(hit.X2), m(hit.Y2))
|
||||
// } else {
|
||||
// coord = fmt.Sprintf("{%f,%f}", m(hit.X), m(hit.Y))
|
||||
// }
|
||||
// fmt.Println("hit:", hit.ID, "Coord:", coord)
|
||||
switch hit.Kind {
|
||||
case world.KindPoint:
|
||||
case world.KindCircle:
|
||||
e.onHitCircle(hit.ID)
|
||||
case world.KindLine:
|
||||
}
|
||||
}
|
||||
|
||||
func (e *client) onHitCircle(id world.PrimitiveID) {
|
||||
p, ok := e.reg.localPlanet(id)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
e.calculator.LoadPlanet(p.Name, p.Number, p.FreeIndustry.F(), p.Material.F(), p.Resources.F())
|
||||
e.calculator.Refresh()
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"galaxy/client/world"
|
||||
"galaxy/model/report"
|
||||
)
|
||||
|
||||
const (
|
||||
entityClassUnknown int = iota - 1
|
||||
entityClassLocalPlanet
|
||||
entityClassOthersPlanet
|
||||
entityClassFreePlanet
|
||||
entityClassUnidentifiedPlanet
|
||||
)
|
||||
|
||||
type registry struct {
|
||||
report *report.Report
|
||||
localPlanetIndex map[world.PrimitiveID]int
|
||||
unidentifiedPlanetIndex map[world.PrimitiveID]int
|
||||
}
|
||||
|
||||
func newRegistry() *registry {
|
||||
return ®istry{
|
||||
localPlanetIndex: make(map[world.PrimitiveID]int),
|
||||
unidentifiedPlanetIndex: make(map[world.PrimitiveID]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) clear(report *report.Report) {
|
||||
r.report = report
|
||||
clear(r.localPlanetIndex)
|
||||
clear(r.unidentifiedPlanetIndex)
|
||||
}
|
||||
|
||||
func (r *registry) entityClass(id world.PrimitiveID) int {
|
||||
if r.isLocalPlanet(id) {
|
||||
return entityClassLocalPlanet
|
||||
}
|
||||
if r.isUnidentifiedPlanet(id) {
|
||||
return entityClassUnidentifiedPlanet
|
||||
}
|
||||
return entityClassUnknown
|
||||
}
|
||||
|
||||
func (r *registry) registerLocalPlanet(id world.PrimitiveID, index int) {
|
||||
r.localPlanetIndex[id] = index
|
||||
}
|
||||
|
||||
func (r *registry) isLocalPlanet(id world.PrimitiveID) bool {
|
||||
_, ok := r.localPlanetIndex[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (r *registry) localPlanet(id world.PrimitiveID) (*report.LocalPlanet, bool) {
|
||||
i, ok := r.localPlanetIndex[id]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
if i > len(r.report.LocalPlanet)-1 {
|
||||
return nil, false
|
||||
}
|
||||
return &r.report.LocalPlanet[i], true
|
||||
}
|
||||
|
||||
func (r *registry) registerUnidentifiedPlanet(id world.PrimitiveID, index int) {
|
||||
r.unidentifiedPlanetIndex[id] = index
|
||||
}
|
||||
|
||||
func (r *registry) isUnidentifiedPlanet(id world.PrimitiveID) bool {
|
||||
_, ok := r.unidentifiedPlanetIndex[id]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (c *client) createShipClass(n string, D float64, A uint, W float64, S float64, C float64) {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"galaxy/client/widget/calculator"
|
||||
"galaxy/client/world"
|
||||
mc "galaxy/model/client"
|
||||
"galaxy/model/report"
|
||||
"slices"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func (e *client) initLatestReport() {
|
||||
e.stateMu.Lock()
|
||||
if e.state.ActiveGameID != nil {
|
||||
if stateIdx := slices.IndexFunc(e.state.GameState, func(gs mc.GameState) bool { return gs.ID == *e.state.ActiveGameID }); stateIdx >= 0 {
|
||||
e.initReportAsync(*e.state.ActiveGameID, e.state.GameState[stateIdx].ActiveTurn)
|
||||
}
|
||||
}
|
||||
e.stateMu.Unlock()
|
||||
}
|
||||
|
||||
func (e *client) initReportAsync(gid mc.GameID, t uint) {
|
||||
e.s.ReportExistsAsync(gid, t, func(b bool, err error) { e.reportAtStorageExists(gid, t, b, err) })
|
||||
}
|
||||
|
||||
func (e *client) reportAtStorageExists(gid mc.GameID, t uint, exists bool, err error) {
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
e.s.LoadReportAsync(gid, t, func(r report.Report, err error) { e.loadReportHandler(gid, r, err) })
|
||||
return
|
||||
}
|
||||
e.conn.FetchReport(gid, t, func(r report.Report, err error) { e.fetchReportHandler(gid, r, err) })
|
||||
}
|
||||
|
||||
func (e *client) fetchReportHandler(gid mc.GameID, r report.Report, err error) {
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
e.s.SaveReportAsync(gid, r.Turn, r, func(err error) { e.loadReportHandler(gid, r, err) })
|
||||
}
|
||||
|
||||
func (e *client) loadReportHandler(gid mc.GameID, r report.Report, err error) {
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
|
||||
e.stateMu.Lock()
|
||||
needSaveState := false
|
||||
stateIdx := slices.IndexFunc(e.state.GameState, func(gs mc.GameState) bool { return gs.ID == gid })
|
||||
if stateIdx < 0 {
|
||||
e.state.GameState = append(e.state.GameState, mc.GameState{ID: gid, LastTurn: r.Turn, ActiveTurn: r.Turn})
|
||||
stateIdx = len(e.state.GameState) - 1
|
||||
needSaveState = true
|
||||
}
|
||||
if e.state.ActiveGameID == nil {
|
||||
e.state.ActiveGameID = new(gid)
|
||||
needSaveState = true
|
||||
}
|
||||
if e.state.GameState[stateIdx].LastTurn < r.Turn {
|
||||
e.state.GameState[stateIdx].LastTurn = r.Turn
|
||||
e.state.GameState[stateIdx].ActiveTurn = r.Turn
|
||||
needSaveState = true
|
||||
}
|
||||
if needSaveState {
|
||||
if err := e.s.SaveState(*e.state); err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
e.stateMu.Unlock()
|
||||
|
||||
e.setReport(r)
|
||||
}
|
||||
|
||||
func (e *client) setReport(r report.Report) {
|
||||
w := world.NewWorld(int(r.Width), int(r.Height))
|
||||
e.reg.clear(&r)
|
||||
for i := range r.LocalPlanet {
|
||||
p := r.LocalPlanet[i]
|
||||
id, err := w.AddCircle(p.X.F(), p.Y.F(), p.Size.F(), world.CircleWithClass(world.CircleClassLocalPlanet))
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
e.reg.registerLocalPlanet(id, i)
|
||||
}
|
||||
for i := range r.UnidentifiedPlanet {
|
||||
p := r.UnidentifiedPlanet[i]
|
||||
id, err := w.AddPoint(p.X.F(), p.Y.F(), world.PointWithClass(world.PointClassTrackIncoming))
|
||||
if err != nil {
|
||||
e.handlerError(err)
|
||||
return
|
||||
}
|
||||
e.reg.registerUnidentifiedPlanet(id, i)
|
||||
}
|
||||
e.loadWorld(w)
|
||||
|
||||
selfIdx := slices.IndexFunc(r.Player, func(p report.Player) bool { return p.Name == r.Race })
|
||||
if selfIdx >= 0 {
|
||||
fyne.Do(func() {
|
||||
e.calculator.Init(
|
||||
calculator.WithPlayerDrives(r.Player[selfIdx].Drive.F()),
|
||||
calculator.WithPlayerWeapons(r.Player[selfIdx].Weapons.F()),
|
||||
calculator.WithPlayerShields(r.Player[selfIdx].Shields.F()),
|
||||
calculator.WithPlayerCargo(r.Player[selfIdx].Cargo.F()),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
e.OnServiceError(fmt.Errorf("race %q not found at report players list", r.Race))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"title": {
|
||||
"map": "Map",
|
||||
"calculator": "Ship Calculator",
|
||||
"info": "Info"
|
||||
},
|
||||
"planet": {
|
||||
"title": "Planet #{{.Number}} '{{.Name}}' production fot this ship:",
|
||||
"mat": "Materials",
|
||||
"prod.mass": "Prod. Mass",
|
||||
"prod.ships": "Ships"
|
||||
},
|
||||
"tech": {
|
||||
"d": "Drive",
|
||||
"w": "Weapons",
|
||||
"s": "Shields",
|
||||
"c": "Cargo"
|
||||
},
|
||||
"ship": {
|
||||
"action.create": "Create",
|
||||
"mass": "Mass",
|
||||
"speed": "Speed",
|
||||
"attack": "Attack",
|
||||
"defense": "Defense",
|
||||
"load": "Load"
|
||||
},
|
||||
"label": {
|
||||
"max": "Max."
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
@@ -256,36 +255,6 @@ func (e *client) onDradEnd() {
|
||||
e.pan.DragEnd()
|
||||
}
|
||||
|
||||
func (e *client) onTapped(ev *fyne.PointEvent) {
|
||||
if e.world == nil || ev == nil {
|
||||
return
|
||||
}
|
||||
|
||||
xPx, yPx, ok := e.eventPosToPixel(ev.Position.X, ev.Position.Y)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
params := e.getLastRenderedParams()
|
||||
hits, err := e.world.HitTest(e.hits, ¶ms, xPx, yPx)
|
||||
if err != nil {
|
||||
// In UI you probably don't want panic; keep your existing handling.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
m := func(v int) float64 { return float64(v) / float64(world.SCALE) }
|
||||
|
||||
for _, hit := range hits {
|
||||
var coord string
|
||||
if hit.Kind == world.KindLine {
|
||||
coord = fmt.Sprintf("{%f,%f - %f,%f}", m(hit.X1), m(hit.Y1), m(hit.X2), m(hit.Y2))
|
||||
} else {
|
||||
coord = fmt.Sprintf("{%f,%f}", m(hit.X), m(hit.Y))
|
||||
}
|
||||
fmt.Println("hit:", hit.ID, "Coord:", coord)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *client) onScrolled(s *fyne.ScrollEvent) {
|
||||
if e.world == nil || s == nil {
|
||||
return
|
||||
|
||||
@@ -0,0 +1,629 @@
|
||||
package calculator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"galaxy/calc"
|
||||
"galaxy/client/widget/numeric"
|
||||
"galaxy/util"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type CalculatorOpt func(*Calculator)
|
||||
type ShipClass struct {
|
||||
Name string
|
||||
Drive float64
|
||||
Armament uint
|
||||
Weapons float64
|
||||
Shields float64
|
||||
Cargo float64
|
||||
}
|
||||
type ShipClassFn func(string, float64, uint, float64, float64, float64)
|
||||
|
||||
type Calculator struct {
|
||||
CanvasObject fyne.CanvasObject
|
||||
|
||||
playerDrivesTech float64
|
||||
playerWeaponsTech float64
|
||||
playerShieldsTech float64
|
||||
playerCargoTech float64
|
||||
|
||||
shipDriveEntry *numeric.FloatEntry
|
||||
shipWeaponsEntry *numeric.FloatEntry
|
||||
shipArmamentEntry *numeric.IntEntry
|
||||
shipShieldsEntry *numeric.FloatEntry
|
||||
shipCargoEntry *numeric.FloatEntry
|
||||
|
||||
playerDrivesTechEntry *numeric.FloatEntry
|
||||
playerWeaponsTechEntry *numeric.FloatEntry
|
||||
playerShieldsTechEntry *numeric.FloatEntry
|
||||
playerCargoTechEntry *numeric.FloatEntry
|
||||
|
||||
drivesTechOverride *widget.Check
|
||||
weaponsTechOverride *widget.Check
|
||||
shieldsTechOverride *widget.Check
|
||||
cargoTechOverride *widget.Check
|
||||
|
||||
massEntry *numeric.FloatEntry
|
||||
speedEntry *numeric.FloatEntry
|
||||
attackEntry *numeric.FloatEntry
|
||||
defenseEntry *numeric.FloatEntry
|
||||
cargoLoadEntry *numeric.FloatEntry
|
||||
planetMatEntry *numeric.FloatEntry
|
||||
|
||||
massOverride *widget.Check
|
||||
speedOverride *widget.Check
|
||||
attackOverride *widget.Check
|
||||
defenseOverride *widget.Check
|
||||
cargoLoadMaximize *widget.Check
|
||||
planetMatOverride *widget.Check
|
||||
|
||||
planetLabel *widget.Label
|
||||
planetMassProdLabel *widget.Label
|
||||
planetShipsProdLabel *widget.Label
|
||||
planetContainer fyne.CanvasObject
|
||||
planetProdContainer fyne.CanvasObject
|
||||
|
||||
shipSelector *widget.SelectEntry
|
||||
shipCreateButton *widget.Button
|
||||
|
||||
onCreateHandler ShipClassFn
|
||||
loader ShipClassFn
|
||||
knownClasses []ShipClass
|
||||
|
||||
validateMu sync.RWMutex
|
||||
|
||||
l, mat, res float64
|
||||
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func WithPlayerDrives(v float64) CalculatorOpt {
|
||||
return func(c *Calculator) { c.playerDrivesTech = v }
|
||||
}
|
||||
|
||||
func WithPlayerWeapons(v float64) CalculatorOpt {
|
||||
return func(c *Calculator) { c.playerWeaponsTech = v }
|
||||
}
|
||||
|
||||
func WithPlayerShields(v float64) CalculatorOpt {
|
||||
return func(c *Calculator) { c.playerShieldsTech = v }
|
||||
}
|
||||
|
||||
func WithPlayerCargo(v float64) CalculatorOpt {
|
||||
return func(c *Calculator) { c.playerCargoTech = v }
|
||||
}
|
||||
|
||||
func WithCreateHandler(f ShipClassFn) CalculatorOpt {
|
||||
return func(c *Calculator) { c.onCreateHandler = f }
|
||||
}
|
||||
|
||||
func NewCaclulator(opts ...CalculatorOpt) *Calculator {
|
||||
c := &Calculator{}
|
||||
|
||||
c.shipCreateButton = widget.NewButton(lang.L("ship.action.create"), c.onCreateShipClassButton)
|
||||
c.shipCreateButton.Disable()
|
||||
c.loader = c.LoadShipClass
|
||||
|
||||
c.planetMatEntry = numeric.NewFloatEntry(10, c.onPlanetMatChange)
|
||||
c.planetMatOverride = widget.NewCheck("", c.overridePlanetMat)
|
||||
c.planetMatOverride.Disable()
|
||||
c.planetLabel = widget.NewLabel("")
|
||||
c.planetMassProdLabel = bareLabel("")
|
||||
c.planetShipsProdLabel = bareLabel("")
|
||||
c.planetProdContainer = container.NewHBox(
|
||||
label(lang.L("planet.prod.mass")+":"),
|
||||
fixedLabel(c.planetMassProdLabel, 80),
|
||||
label(lang.L("planet.prod.ships")+":"),
|
||||
fixedLabel(c.planetShipsProdLabel, 80),
|
||||
)
|
||||
c.planetProdContainer.Hide()
|
||||
|
||||
c.planetContainer = container.NewVBox(
|
||||
widget.NewSeparator(),
|
||||
container.NewHBox(c.planetLabel),
|
||||
rowForItem(lang.L("planet.mat")+":", floatEntry(c.planetMatEntry, 100), c.planetMatOverride),
|
||||
c.planetProdContainer,
|
||||
)
|
||||
c.planetContainer.Hide()
|
||||
|
||||
c.shipSelector = widget.NewSelectEntry(nil)
|
||||
c.shipSelector.OnChanged = c.onShipSelectorChange
|
||||
|
||||
c.shipDriveEntry = numeric.NewFloatEntry(7, c.onShipDriveChange)
|
||||
c.shipWeaponsEntry = numeric.NewFloatEntry(7, c.onShipWeaponsChange)
|
||||
c.shipArmamentEntry = numeric.NewIntEntry(7, c.onShipArmamentChange)
|
||||
c.shipShieldsEntry = numeric.NewFloatEntry(7, c.onShipShieldsChange)
|
||||
c.shipCargoEntry = numeric.NewFloatEntry(7, c.onShipCargoChange)
|
||||
|
||||
c.playerDrivesTechEntry = numeric.NewFloatEntry(7, c.onDrivesTechChange)
|
||||
c.playerWeaponsTechEntry = numeric.NewFloatEntry(7, c.onWeaponsTechChange)
|
||||
c.playerShieldsTechEntry = numeric.NewFloatEntry(7, c.onShieldsTechChange)
|
||||
c.playerCargoTechEntry = numeric.NewFloatEntry(7, c.onCargoTechChange)
|
||||
|
||||
c.massEntry = numeric.NewFloatEntry(7, c.onMassChange)
|
||||
c.speedEntry = numeric.NewFloatEntry(7, c.onSpeedChange)
|
||||
c.attackEntry = numeric.NewFloatEntry(7, c.onAttackChange)
|
||||
c.defenseEntry = numeric.NewFloatEntry(7, c.onDefenseChange)
|
||||
c.cargoLoadEntry = numeric.NewFloatEntry(7, c.onCargoLoadChange)
|
||||
|
||||
c.drivesTechOverride = widget.NewCheck("", c.overrideDrivesTech)
|
||||
c.drivesTechOverride.Disable()
|
||||
c.weaponsTechOverride = widget.NewCheck("", c.overrideWeaponsTech)
|
||||
c.weaponsTechOverride.Disable()
|
||||
c.shieldsTechOverride = widget.NewCheck("", c.overrideShieldsTech)
|
||||
c.shieldsTechOverride.Disable()
|
||||
c.cargoTechOverride = widget.NewCheck("", c.overrideCargoTech)
|
||||
c.cargoTechOverride.Disable()
|
||||
|
||||
c.massOverride = widget.NewCheck("", c.overrideMass)
|
||||
c.massOverride.Disable()
|
||||
c.speedOverride = widget.NewCheck("", c.overrideSpeed)
|
||||
c.speedOverride.Disable()
|
||||
c.attackOverride = widget.NewCheck("", c.overrideAttack)
|
||||
c.attackOverride.Disable()
|
||||
c.defenseOverride = widget.NewCheck("", c.overrideDefense)
|
||||
c.defenseOverride.Disable()
|
||||
c.cargoLoadMaximize = widget.NewCheck(lang.L("label.max"), c.maximizeCargoLoad)
|
||||
c.cargoLoadMaximize.SetChecked(true)
|
||||
|
||||
createShip := container.NewBorder(
|
||||
nil, // top
|
||||
nil, // bottom
|
||||
nil, // left
|
||||
c.shipCreateButton, // right
|
||||
c.shipSelector, // center
|
||||
)
|
||||
|
||||
c.CanvasObject = container.NewVBox(
|
||||
container.NewPadded(createShip),
|
||||
widget.NewSeparator(),
|
||||
rowForTech(lang.L("tech.d")+":",
|
||||
c.shipDriveEntry, floatEntry(c.playerDrivesTechEntry, 80), c.drivesTechOverride),
|
||||
rowForWeapons(lang.L("tech.w")+":",
|
||||
c.shipArmamentEntry, c.shipWeaponsEntry, floatEntry(c.playerWeaponsTechEntry, 80), c.weaponsTechOverride),
|
||||
rowForTech(lang.L("tech.s")+":",
|
||||
c.shipShieldsEntry, floatEntry(c.playerShieldsTechEntry, 80), c.shieldsTechOverride),
|
||||
rowForTech(lang.L("tech.c")+":",
|
||||
c.shipCargoEntry, floatEntry(c.playerCargoTechEntry, 80), c.cargoTechOverride),
|
||||
widget.NewSeparator(),
|
||||
rowForItem(lang.L("ship.load")+":",
|
||||
floatEntry(c.cargoLoadEntry, 80), c.cargoLoadMaximize),
|
||||
rowForItem(lang.L("ship.mass")+":",
|
||||
floatEntry(c.massEntry, 80), c.massOverride),
|
||||
rowForItem(lang.L("ship.speed")+":",
|
||||
floatEntry(c.speedEntry, 80), c.speedOverride),
|
||||
rowForItem(lang.L("ship.attack")+":",
|
||||
floatEntry(c.attackEntry, 80), c.attackOverride),
|
||||
rowForItem(lang.L("ship.defense")+":",
|
||||
floatEntry(c.defenseEntry, 80), c.defenseOverride),
|
||||
c.planetContainer,
|
||||
)
|
||||
|
||||
c.Init(opts...)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Calculator) Init(opts ...CalculatorOpt) {
|
||||
for i := range opts {
|
||||
opts[i](c)
|
||||
}
|
||||
|
||||
c.playerDrivesTechEntry.SetOrigin(c.playerDrivesTech)
|
||||
c.playerWeaponsTechEntry.SetOrigin(c.playerWeaponsTech)
|
||||
c.playerShieldsTechEntry.SetOrigin(c.playerShieldsTech)
|
||||
c.playerCargoTechEntry.SetOrigin(c.playerCargoTech)
|
||||
|
||||
c.CanvasObject.Show()
|
||||
}
|
||||
|
||||
func (c *Calculator) Refresh() {
|
||||
c.validate()
|
||||
c.CanvasObject.Refresh()
|
||||
}
|
||||
|
||||
func (c *Calculator) RegisterClasses(shipClass ...ShipClass) {
|
||||
c.knownClasses = shipClass
|
||||
names := make([]string, len(c.knownClasses))
|
||||
for i := range c.knownClasses {
|
||||
names[i] = c.knownClasses[i].Name
|
||||
}
|
||||
slices.Sort(names)
|
||||
c.shipSelector = widget.NewSelectEntry(names)
|
||||
c.shipSelector.OnChanged = c.onShipSelectorChange
|
||||
}
|
||||
|
||||
func (c *Calculator) onCreateShipClassButton() {
|
||||
if c.onCreateHandler == nil || !c.Valid {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Calculator) validate() {
|
||||
fyne.Do(func() {
|
||||
c.validateMu.Lock()
|
||||
err := c.validateEntries()
|
||||
c.Valid = err == nil
|
||||
if err != nil {
|
||||
} else {
|
||||
}
|
||||
c.shipClassNameValidate()
|
||||
c.validateMu.Unlock()
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Calculator) validateEntries() (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.cargoLoadEntry.Clear()
|
||||
if !c.massOverride.Checked {
|
||||
c.massEntry.Clear()
|
||||
}
|
||||
if !c.speedOverride.Checked {
|
||||
c.speedEntry.Clear()
|
||||
}
|
||||
if !c.attackOverride.Checked {
|
||||
c.attackEntry.Clear()
|
||||
}
|
||||
if !c.defenseOverride.Checked {
|
||||
c.defenseEntry.Clear()
|
||||
}
|
||||
// c.planetProdContainer.Hide()
|
||||
}
|
||||
}()
|
||||
drive, ok := c.shipDriveEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Parameter Drive is not valid")
|
||||
return
|
||||
}
|
||||
driveTech, ok := c.playerDrivesTechEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Drive tech level is not valid")
|
||||
return
|
||||
}
|
||||
armament, ok := c.shipArmamentEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Parameter Armament is not valid")
|
||||
return
|
||||
}
|
||||
weapons, ok := c.shipWeaponsEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Parameter Weapons is not valid")
|
||||
return
|
||||
}
|
||||
weaponsTech, ok := c.playerWeaponsTechEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Weapons tech level is not valid")
|
||||
return
|
||||
}
|
||||
shields, ok := c.shipShieldsEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Parameter Shields is not valid")
|
||||
return
|
||||
}
|
||||
shieldsTech, ok := c.playerShieldsTechEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Shields tech level is not valid")
|
||||
return
|
||||
}
|
||||
cargo, ok := c.shipCargoEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Parameter Cargo is not valid")
|
||||
return
|
||||
}
|
||||
cargoTech, ok := c.playerCargoTechEntry.Value()
|
||||
if !ok {
|
||||
err = errors.New("Cargo tech level is not valid")
|
||||
return
|
||||
}
|
||||
|
||||
err = calc.ValidateShipTypeValues(drive, armament, weapons, shields, cargo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var cargoLoad float64
|
||||
if c.cargoLoadMaximize.Checked {
|
||||
cargoLoad = calc.CargoCapacity(cargo, cargoTech)
|
||||
c.cargoLoadEntry.SetOrigin(cargoLoad)
|
||||
} else if cargoLoad, ok = c.cargoLoadEntry.Value(); !ok {
|
||||
err = errors.New("Cargo load value is not valid")
|
||||
return
|
||||
}
|
||||
|
||||
emptyMass, ok := calc.EmptyMass(drive, weapons, uint(armament), shields, cargo)
|
||||
if !ok {
|
||||
err = errors.New("Unable to calculate empty mass (check armament and weapons)")
|
||||
return
|
||||
}
|
||||
fullMass := calc.FullMass(emptyMass, cargoLoad)
|
||||
speed := calc.Speed(calc.DriveEffective(drive, driveTech), fullMass)
|
||||
effectiveAttack := calc.EffectiveAttack(weapons, weaponsTech)
|
||||
effectiveDefense := calc.EffectiveDefence(shields, shieldsTech, fullMass)
|
||||
|
||||
c.massEntry.SetOrigin(emptyMass)
|
||||
c.speedEntry.SetOrigin(speed)
|
||||
c.attackEntry.SetOrigin(effectiveAttack)
|
||||
c.defenseEntry.SetOrigin(effectiveDefense)
|
||||
|
||||
planetMat, ok := c.planetMatEntry.Value()
|
||||
if !ok {
|
||||
// c.planetProdContainer.Hide()
|
||||
} else {
|
||||
massProd := calc.PlanetProduceShipMass(c.l, planetMat, c.res)
|
||||
c.planetMassProdLabel.SetText(strconv.FormatFloat(util.Fixed3(massProd), 'f', -1, 64))
|
||||
ships := 0.
|
||||
if emptyMass > 0 {
|
||||
ships = massProd / emptyMass
|
||||
}
|
||||
c.planetShipsProdLabel.SetText(strconv.FormatFloat(util.Fixed3(ships), 'f', -1, 64))
|
||||
c.planetProdContainer.Show()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Calculator) onOriginInputChange(cb *widget.Check, e *numeric.FloatEntry) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
if cb != nil {
|
||||
cb.Checked = e.Overriden()
|
||||
if !cb.Checked {
|
||||
cb.Disable()
|
||||
} else {
|
||||
cb.Enable()
|
||||
}
|
||||
}
|
||||
c.onFloatEntryChange(e)
|
||||
}
|
||||
|
||||
func (c *Calculator) onFloatEntryChange(e *numeric.FloatEntry) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Validate()
|
||||
c.validate()
|
||||
}
|
||||
|
||||
func (c *Calculator) onIntEntryChange(e *numeric.IntEntry) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.Validate()
|
||||
c.validate()
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideChecked(cb *widget.Check, e *numeric.FloatEntry) {
|
||||
if cb == nil || e == nil {
|
||||
return
|
||||
}
|
||||
if !cb.Checked {
|
||||
e.Reset()
|
||||
cb.Disable()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Calculator) onShipDriveChange(string) {
|
||||
c.onFloatEntryChange(c.shipDriveEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onShipArmamentChange(string) {
|
||||
defer c.onIntEntryChange(c.shipArmamentEntry)
|
||||
if weapons, ok := c.shipWeaponsEntry.Value(); !ok || !c.shipWeaponsEntry.Valid {
|
||||
return
|
||||
} else if armament, ok := c.shipArmamentEntry.Value(); !ok || !c.shipArmamentEntry.Valid {
|
||||
return
|
||||
} else if armament > 0 && weapons == 0 {
|
||||
c.shipWeaponsEntry.SetOrigin(1.0)
|
||||
} else if armament == 0 && weapons > 0 {
|
||||
c.shipWeaponsEntry.SetOrigin(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Calculator) onShipWeaponsChange(string) {
|
||||
defer c.onFloatEntryChange(c.shipWeaponsEntry)
|
||||
if weapons, ok := c.shipWeaponsEntry.Value(); !ok || !c.shipWeaponsEntry.Valid {
|
||||
return
|
||||
} else if armament, ok := c.shipArmamentEntry.Value(); !ok || !c.shipArmamentEntry.Valid {
|
||||
return
|
||||
} else if weapons > 0 && armament == 0 {
|
||||
c.shipArmamentEntry.SetOrigin(1)
|
||||
} else if weapons == 0 && armament > 0 {
|
||||
c.shipArmamentEntry.SetOrigin(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Calculator) onShipShieldsChange(string) {
|
||||
c.onFloatEntryChange(c.shipShieldsEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onShipCargoChange(string) {
|
||||
c.onFloatEntryChange(c.shipCargoEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onDrivesTechChange(string) {
|
||||
c.onOriginInputChange(c.drivesTechOverride, c.playerDrivesTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideDrivesTech(bool) {
|
||||
c.overrideChecked(c.drivesTechOverride, c.playerDrivesTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onWeaponsTechChange(string) {
|
||||
c.onOriginInputChange(c.weaponsTechOverride, c.playerWeaponsTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideWeaponsTech(bool) {
|
||||
c.overrideChecked(c.weaponsTechOverride, c.playerWeaponsTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onShieldsTechChange(string) {
|
||||
c.onOriginInputChange(c.shieldsTechOverride, c.playerShieldsTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideShieldsTech(bool) {
|
||||
c.overrideChecked(c.shieldsTechOverride, c.playerShieldsTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onCargoTechChange(string) {
|
||||
c.onOriginInputChange(c.cargoTechOverride, c.playerCargoTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideCargoTech(bool) {
|
||||
c.overrideChecked(c.cargoTechOverride, c.playerCargoTechEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onCargoLoadChange(string) {
|
||||
c.onFloatEntryChange(c.cargoLoadEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onMassChange(string) {
|
||||
c.onOriginInputChange(c.massOverride, c.massEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideMass(bool) {
|
||||
c.overrideChecked(c.massOverride, c.massEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onSpeedChange(string) {
|
||||
c.onOriginInputChange(c.speedOverride, c.speedEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideSpeed(bool) {
|
||||
c.overrideChecked(c.speedOverride, c.speedEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onAttackChange(string) {
|
||||
c.onOriginInputChange(c.attackOverride, c.attackEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideAttack(bool) {
|
||||
c.overrideChecked(c.attackOverride, c.attackEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onDefenseChange(string) {
|
||||
c.onOriginInputChange(c.defenseOverride, c.defenseEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overrideDefense(bool) {
|
||||
c.overrideChecked(c.defenseOverride, c.defenseEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) maximizeCargoLoad(bool) {
|
||||
c.validate()
|
||||
}
|
||||
|
||||
func (c *Calculator) onPlanetMatChange(string) {
|
||||
c.onOriginInputChange(c.planetMatOverride, c.planetMatEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) overridePlanetMat(bool) {
|
||||
c.overrideChecked(c.planetMatOverride, c.planetMatEntry)
|
||||
}
|
||||
|
||||
func (c *Calculator) onShipSelectorChange(v string) {
|
||||
i, ok := c.shipClassNameValidate()
|
||||
if i < 0 || !ok || c.loader == nil {
|
||||
return
|
||||
}
|
||||
c.loader(
|
||||
c.knownClasses[i].Name,
|
||||
c.knownClasses[i].Drive,
|
||||
c.knownClasses[i].Armament,
|
||||
c.knownClasses[i].Weapons,
|
||||
c.knownClasses[i].Shields,
|
||||
c.knownClasses[i].Cargo,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Calculator) shipClassNameValidate() (int, bool) {
|
||||
var canCreateShip bool
|
||||
defer func() {
|
||||
if canCreateShip && c.Valid {
|
||||
c.shipCreateButton.Enable()
|
||||
} else {
|
||||
c.shipCreateButton.Disable()
|
||||
}
|
||||
}()
|
||||
name, canCreateShip := util.ValidateTypeName(c.shipSelector.Text)
|
||||
if canCreateShip {
|
||||
c.shipSelector.Text = name
|
||||
}
|
||||
i := slices.IndexFunc(c.knownClasses, func(v ShipClass) bool { return v.Name == name })
|
||||
canCreateShip = canCreateShip && i < 0
|
||||
return i, canCreateShip
|
||||
}
|
||||
|
||||
func (c *Calculator) LoadShipClass(n string, D float64, A uint, W float64, S float64, C float64) {
|
||||
c.shipDriveEntry.SetOrigin(D)
|
||||
c.shipArmamentEntry.SetOrigin(int(A))
|
||||
c.shipWeaponsEntry.SetOrigin(W)
|
||||
c.shipShieldsEntry.SetOrigin(S)
|
||||
c.shipCargoEntry.SetOrigin(C)
|
||||
}
|
||||
|
||||
func rowForItem(l string, entry, override fyne.CanvasObject) fyne.CanvasObject {
|
||||
i := []fyne.CanvasObject{label(l), entry}
|
||||
if override != nil {
|
||||
i = append(i, override)
|
||||
}
|
||||
return container.NewHBox(i...)
|
||||
}
|
||||
|
||||
func rowForTech(l string, shipEntry, techEntry, btn fyne.CanvasObject) fyne.CanvasObject {
|
||||
return container.NewHBox(
|
||||
label(l),
|
||||
floatEntry(shipEntry, 115),
|
||||
widget.NewLabel("@"),
|
||||
techEntry,
|
||||
btn,
|
||||
)
|
||||
}
|
||||
|
||||
func rowForWeapons(l string, armamentEntry, weaponsEntry, techEntry, btn fyne.CanvasObject) fyne.CanvasObject {
|
||||
return container.NewHBox(
|
||||
label(l),
|
||||
intEntry(armamentEntry, 35),
|
||||
floatEntry(weaponsEntry, 75),
|
||||
widget.NewLabel("@"),
|
||||
techEntry,
|
||||
btn,
|
||||
)
|
||||
}
|
||||
|
||||
func label(l string) fyne.CanvasObject {
|
||||
return fixedLabel(bareLabel(l), 110)
|
||||
}
|
||||
|
||||
func fixedLabel(w *widget.Label, width float32) fyne.CanvasObject {
|
||||
s := container.NewHScroll(w)
|
||||
s.SetMinSize(fyne.NewSize(width, 1))
|
||||
return s
|
||||
}
|
||||
|
||||
func bareLabel(l string) *widget.Label {
|
||||
w := widget.NewLabelWithStyle(l, fyne.TextAlignTrailing, fyne.TextStyle{Monospace: true, Symbol: false})
|
||||
w.Selectable = false
|
||||
w.Truncation = fyne.TextTruncateOff
|
||||
return w
|
||||
}
|
||||
|
||||
func intEntry(content fyne.CanvasObject, width float32) fyne.CanvasObject {
|
||||
s := container.NewHScroll(content)
|
||||
s.SetMinSize(fyne.NewSize(width, 1))
|
||||
return s
|
||||
}
|
||||
|
||||
func floatEntry(content fyne.CanvasObject, width float32) fyne.CanvasObject {
|
||||
s := container.NewHScroll(content)
|
||||
s.SetMinSize(fyne.NewSize(width, 1))
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package calculator
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2/lang"
|
||||
)
|
||||
|
||||
func (c *Calculator) UnloadPlanet() {
|
||||
c.planetContainer.Hide()
|
||||
}
|
||||
|
||||
func (c *Calculator) LoadPlanet(name string, number uint, L, Mat, Res float64) {
|
||||
c.l, c.mat, c.res = L, Mat, Res
|
||||
c.planetLabel.SetText(lang.L("planet.title", map[string]any{"Number": number, "Name": name}))
|
||||
c.planetMatEntry.SetOrigin(Mat)
|
||||
c.planetContainer.Show()
|
||||
}
|
||||
@@ -1,30 +1,82 @@
|
||||
package numeric
|
||||
|
||||
import (
|
||||
"galaxy/client/widget/validator"
|
||||
"galaxy/util"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/driver/mobile"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type numericalEntry struct {
|
||||
type FloatEntry struct {
|
||||
widget.Entry
|
||||
origin float64
|
||||
MaxValue float64
|
||||
maxSize uint
|
||||
validator fyne.StringValidator
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func NewNumericalEntry() *numericalEntry {
|
||||
entry := &numericalEntry{}
|
||||
entry.ExtendBaseWidget(entry)
|
||||
return entry
|
||||
type IntEntry struct {
|
||||
widget.Entry
|
||||
origin uint
|
||||
MaxValue uint
|
||||
maxSize uint
|
||||
validator fyne.StringValidator
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (e *numericalEntry) TypedRune(r rune) {
|
||||
if (r >= '0' && r <= '9') || r == '.' || r == ',' {
|
||||
e.Entry.TypedRune(r)
|
||||
func NewFloatEntry(maxSize uint, onChanged func(string)) *FloatEntry {
|
||||
e := &FloatEntry{maxSize: maxSize, validator: validator.FloatEntryValidator}
|
||||
e.ExtendBaseWidget(e)
|
||||
e.Entry.Scroll = fyne.ScrollNone
|
||||
e.Entry.TextStyle = fyne.TextStyle{Monospace: true}
|
||||
// e.Validator = validator.FloatEntryValidator
|
||||
// e.AlwaysShowValidationError = true
|
||||
e.Entry.ActionItem = nil
|
||||
e.SetOrigin(0)
|
||||
e.Validate()
|
||||
e.Entry.OnChanged = onChanged
|
||||
return e
|
||||
}
|
||||
|
||||
func NewIntEntry(maxSize uint, onChanged func(string)) *IntEntry {
|
||||
e := &IntEntry{maxSize: maxSize, validator: validator.IntEntryValidator}
|
||||
e.ExtendBaseWidget(e)
|
||||
e.Entry.Scroll = fyne.ScrollNone
|
||||
e.Entry.TextStyle = fyne.TextStyle{Monospace: true}
|
||||
// e.Validator = validator.IntEntryValidator
|
||||
// e.AlwaysShowValidationError = true
|
||||
e.Entry.ActionItem = nil
|
||||
e.SetOrigin(0)
|
||||
e.Validate()
|
||||
e.Entry.OnChanged = onChanged
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *FloatEntry) CreateRenderer() fyne.WidgetRenderer {
|
||||
r := e.Entry.CreateRenderer()
|
||||
return r
|
||||
}
|
||||
|
||||
func (e *FloatEntry) TypedRune(r rune) {
|
||||
if !((r >= '0' && r <= '9') || r == '.') {
|
||||
return
|
||||
}
|
||||
if !lengthBelowLimit(e.Entry.Text, e.maxSize) && e.Entry.SelectedText() == "" {
|
||||
return
|
||||
}
|
||||
if r == '.' && strings.Contains(e.Entry.Text, ".") {
|
||||
return
|
||||
}
|
||||
e.Entry.TypedRune(r)
|
||||
}
|
||||
|
||||
func (e *numericalEntry) TypedShortcut(shortcut fyne.Shortcut) {
|
||||
func (e *FloatEntry) TypedShortcut(shortcut fyne.Shortcut) {
|
||||
paste, ok := shortcut.(*fyne.ShortcutPaste)
|
||||
if !ok {
|
||||
e.Entry.TypedShortcut(shortcut)
|
||||
@@ -37,6 +89,129 @@ func (e *numericalEntry) TypedShortcut(shortcut fyne.Shortcut) {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *numericalEntry) Keyboard() mobile.KeyboardType {
|
||||
func (e *FloatEntry) Keyboard() mobile.KeyboardType {
|
||||
return mobile.NumberKeyboard
|
||||
}
|
||||
|
||||
func (e *FloatEntry) SetOrigin(v float64) {
|
||||
if v < 0 {
|
||||
return
|
||||
}
|
||||
e.origin = v
|
||||
e.Reset()
|
||||
}
|
||||
|
||||
func (e *FloatEntry) Reset() {
|
||||
e.SetValue(e.origin)
|
||||
}
|
||||
|
||||
func (e *FloatEntry) Clear() {
|
||||
onChanged := e.Entry.OnChanged
|
||||
e.Entry.OnChanged = nil
|
||||
e.Entry.SetText("")
|
||||
e.Entry.OnChanged = onChanged
|
||||
}
|
||||
|
||||
func (e *FloatEntry) SetValue(v float64) {
|
||||
if v < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
e.Entry.SetText(strconv.FormatFloat(util.Fixed3(v), 'f', -1, 64))
|
||||
}
|
||||
|
||||
func (e *FloatEntry) Value() (float64, bool) {
|
||||
if v, err := validator.ParseFloat(e.Entry.Text); err != nil {
|
||||
return 0, false
|
||||
} else {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
func (e *FloatEntry) Overriden() bool {
|
||||
if v, ok := e.Value(); !ok {
|
||||
return false
|
||||
} else {
|
||||
return util.Fixed3(v) != util.Fixed3(e.origin)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *FloatEntry) Validate() {
|
||||
if e.validator == nil {
|
||||
return
|
||||
}
|
||||
err := e.validator(e.Entry.Text)
|
||||
e.Valid = err == nil
|
||||
}
|
||||
|
||||
func (e *IntEntry) TypedRune(r rune) {
|
||||
if r >= '0' && r <= '9' {
|
||||
if lengthBelowLimit(e.Entry.Text, e.maxSize) || e.Entry.SelectedText() != "" {
|
||||
e.Entry.TypedRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (e *IntEntry) TypedShortcut(shortcut fyne.Shortcut) {
|
||||
paste, ok := shortcut.(*fyne.ShortcutPaste)
|
||||
if !ok {
|
||||
e.Entry.TypedShortcut(shortcut)
|
||||
return
|
||||
}
|
||||
|
||||
content := paste.Clipboard.Content()
|
||||
if _, err := strconv.ParseInt(content, 10, 64); err == nil {
|
||||
e.Entry.TypedShortcut(shortcut)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *IntEntry) Keyboard() mobile.KeyboardType {
|
||||
return mobile.NumberKeyboard
|
||||
}
|
||||
|
||||
func (e *IntEntry) SetOrigin(v int) {
|
||||
if v < 0 {
|
||||
return
|
||||
}
|
||||
e.origin = uint(v)
|
||||
e.Reset()
|
||||
}
|
||||
|
||||
func (e *IntEntry) Reset() {
|
||||
e.SetValue(int(e.origin))
|
||||
}
|
||||
|
||||
func (e *IntEntry) SetValue(v int) {
|
||||
if v < 0 {
|
||||
return
|
||||
}
|
||||
e.Entry.SetText(strconv.Itoa(v))
|
||||
}
|
||||
|
||||
func (e *IntEntry) Value() (int, bool) {
|
||||
if v, err := validator.ParseInt(e.Entry.Text); err != nil {
|
||||
return 0, false
|
||||
} else {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
|
||||
func (e *IntEntry) Overriden() bool {
|
||||
if v, ok := e.Value(); !ok {
|
||||
return false
|
||||
} else {
|
||||
return v != int(e.origin)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *IntEntry) Validate() {
|
||||
if e.validator == nil {
|
||||
return
|
||||
}
|
||||
err := e.validator(e.Entry.Text)
|
||||
e.Valid = err == nil
|
||||
}
|
||||
|
||||
func lengthBelowLimit(s string, max uint) bool {
|
||||
return utf8.RuneCountInString(s) < int(max)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,26 @@ package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
type floatValidator func(float64) error
|
||||
|
||||
var (
|
||||
FloatEntryValidator = numericEntryValidator(
|
||||
nonNegativeValidator,
|
||||
minOrZeroValueValidator(1.),
|
||||
)
|
||||
IntEntryValidator = numericEntryValidator(
|
||||
intValidator,
|
||||
nonNegativeValidator,
|
||||
minOrZeroValueValidator(1.),
|
||||
)
|
||||
)
|
||||
|
||||
func NewStackValidator(first fyne.StringValidator, rest ...fyne.StringValidator) fyne.StringValidator {
|
||||
if first == nil {
|
||||
panic("first validator cannot be nil")
|
||||
@@ -43,6 +58,44 @@ func NewMutualValidator(other func() float64, valid func(float64) bool) fyne.Str
|
||||
}
|
||||
}
|
||||
|
||||
func numericEntryValidator(other ...floatValidator) fyne.StringValidator {
|
||||
return func(s string) error {
|
||||
v, err := ParseFloat(s)
|
||||
if err != nil {
|
||||
return errors.New("not a float value")
|
||||
}
|
||||
for i := range other {
|
||||
if err := other[i](v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func nonNegativeValidator(v float64) error {
|
||||
if v < 0 {
|
||||
return errors.New("value must be greater of equal to zero")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func intValidator(v float64) error {
|
||||
if float64(int(v)) != v {
|
||||
return errors.New("value must be an integer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func minOrZeroValueValidator(min float64) floatValidator {
|
||||
return func(f float64) error {
|
||||
if f > 0 && f < min {
|
||||
return fmt.Errorf("value must be zero or >= %f", min)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func FloatValueValidator(s string) error {
|
||||
if _, err := ParseFloat(s); err != nil {
|
||||
return errors.New("not a float value")
|
||||
@@ -50,6 +103,21 @@ func FloatValueValidator(s string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func IntValueValidator(s string) error {
|
||||
if _, err := ParseInt(s); err != nil {
|
||||
return errors.New("not an integer value")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseFloat(s string) (float64, error) {
|
||||
return strconv.ParseFloat(s, 64)
|
||||
}
|
||||
|
||||
func ParseInt(s string) (int, error) {
|
||||
if v, err := strconv.ParseInt(s, 10, 64); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return int(v), nil
|
||||
}
|
||||
}
|
||||
|
||||
+22
-24
@@ -415,6 +415,13 @@ func (LightTheme) PointClassOverride(class PointClassID) (StyleOverride, bool) {
|
||||
PointRadiusPx: new(3.5),
|
||||
}, true
|
||||
|
||||
case PointClassUnidentifiedPlanet:
|
||||
// soft orange
|
||||
return StyleOverride{
|
||||
FillColor: cRGBA(192, 192, 192, 255),
|
||||
PointRadiusPx: new(2.5),
|
||||
}, true
|
||||
|
||||
default:
|
||||
return StyleOverride{}, false
|
||||
}
|
||||
@@ -457,15 +464,7 @@ func (LightTheme) CircleClassOverride(class CircleClassID) (StyleOverride, bool)
|
||||
case CircleClassDefault:
|
||||
return StyleOverride{}, false
|
||||
|
||||
case CircleClassHome:
|
||||
// teal-ish, a bit stronger stroke
|
||||
return StyleOverride{
|
||||
FillColor: cRGBA(32, 161, 145, 50),
|
||||
StrokeColor: cRGBA(32, 161, 145, 210),
|
||||
StrokeWidthPx: new(2.5),
|
||||
}, true
|
||||
|
||||
case CircleClassAcquired:
|
||||
case CircleClassLocalPlanet:
|
||||
// blue
|
||||
return StyleOverride{
|
||||
FillColor: cRGBA(70, 108, 196, 45),
|
||||
@@ -473,7 +472,7 @@ func (LightTheme) CircleClassOverride(class CircleClassID) (StyleOverride, bool)
|
||||
StrokeWidthPx: new(2.2),
|
||||
}, true
|
||||
|
||||
case CircleClassOccupied:
|
||||
case CircleClassOthersPlanet:
|
||||
// orange
|
||||
return StyleOverride{
|
||||
FillColor: cRGBA(222, 142, 70, 50),
|
||||
@@ -481,7 +480,7 @@ func (LightTheme) CircleClassOverride(class CircleClassID) (StyleOverride, bool)
|
||||
StrokeWidthPx: new(2.2),
|
||||
}, true
|
||||
|
||||
case CircleClassFree:
|
||||
case CircleClassFreePlanet:
|
||||
// green
|
||||
return StyleOverride{
|
||||
FillColor: cRGBA(76, 171, 107, 45),
|
||||
@@ -574,6 +573,12 @@ func (*DarkTheme) PointClassOverride(class PointClassID) (StyleOverride, bool) {
|
||||
PointRadiusPx: new(3.5),
|
||||
}, true
|
||||
|
||||
case PointClassUnidentifiedPlanet:
|
||||
return StyleOverride{
|
||||
FillColor: cRGBA(192, 192, 192, 255),
|
||||
PointRadiusPx: new(2.5),
|
||||
}, true
|
||||
|
||||
default:
|
||||
return StyleOverride{}, false
|
||||
}
|
||||
@@ -615,30 +620,23 @@ func (*DarkTheme) CircleClassOverride(class CircleClassID) (StyleOverride, bool)
|
||||
case CircleClassDefault:
|
||||
return StyleOverride{}, false
|
||||
|
||||
case CircleClassHome:
|
||||
case CircleClassLocalPlanet:
|
||||
return StyleOverride{
|
||||
FillColor: nil, // cRGBA(120, 214, 198, 255),
|
||||
StrokeColor: cRGBA(120, 214, 198, 255),
|
||||
StrokeWidthPx: new(2.5),
|
||||
}, true
|
||||
|
||||
case CircleClassAcquired:
|
||||
return StyleOverride{
|
||||
FillColor: nil, // cRGBA(155, 175, 235, 255),
|
||||
FillColor: cRGBA(155, 175, 235, 255),
|
||||
StrokeColor: cRGBA(155, 175, 235, 255),
|
||||
StrokeWidthPx: new(2.2),
|
||||
}, true
|
||||
|
||||
case CircleClassOccupied:
|
||||
case CircleClassOthersPlanet:
|
||||
return StyleOverride{
|
||||
FillColor: nil, // cRGBA(245, 178, 120, 255),
|
||||
FillColor: cRGBA(245, 178, 120, 255),
|
||||
StrokeColor: cRGBA(245, 178, 120, 255),
|
||||
StrokeWidthPx: new(2.2),
|
||||
}, true
|
||||
|
||||
case CircleClassFree:
|
||||
case CircleClassFreePlanet:
|
||||
return StyleOverride{
|
||||
FillColor: nil, // cRGBA(132, 219, 162, 255),
|
||||
FillColor: cRGBA(132, 219, 162, 255),
|
||||
StrokeColor: cRGBA(132, 219, 162, 255),
|
||||
StrokeWidthPx: new(2.2),
|
||||
}, true
|
||||
|
||||
@@ -688,6 +688,8 @@ const (
|
||||
PointClassTrackIncoming
|
||||
// PointClassTrackOutgoing marks a point as an outgoing track marker.
|
||||
PointClassTrackOutgoing
|
||||
// PointClassUnidentifiedPlanet marks an unidentified planet without visivle size.
|
||||
PointClassUnidentifiedPlanet
|
||||
)
|
||||
|
||||
// LineClassID classifies Line primitives for theme-level style overrides.
|
||||
@@ -711,14 +713,12 @@ type CircleClassID uint8
|
||||
const (
|
||||
// CircleClassDefault selects the theme's default circle styling.
|
||||
CircleClassDefault CircleClassID = iota
|
||||
// CircleClassHome marks a circle as a home-world area.
|
||||
CircleClassHome
|
||||
// CircleClassAcquired marks a circle as an acquired world area.
|
||||
CircleClassAcquired
|
||||
// CircleClassOccupied marks a circle as an occupied world area.
|
||||
CircleClassOccupied
|
||||
// CircleClassFree marks a circle as a free world area.
|
||||
CircleClassFree
|
||||
// CircleClassLocalPlanet marks a circle as a player-owned planet.
|
||||
CircleClassLocalPlanet
|
||||
// CircleClassOthersPlanet marks a circle as an occupied planet.
|
||||
CircleClassOthersPlanet
|
||||
// CircleClassFreePlanet marks a circle as a free planet.
|
||||
CircleClassFreePlanet
|
||||
)
|
||||
|
||||
// PrimitiveID is a compact stable identifier for primitives stored in the World.
|
||||
|
||||
Reference in New Issue
Block a user