diff --git a/dial.go b/dial.go new file mode 100644 index 0000000..11ac3c9 --- /dev/null +++ b/dial.go @@ -0,0 +1,137 @@ +package tins2020 + +import ( + "math" + "strconv" + + "opslag.de/schobers/geom" + "opslag.de/schobers/zntg/ui" +) + +type Dial struct { + ui.ContainerBase + + dialer Dialer + + typing string // current digit + digitCount int // number of times the digit is pressed + digits []DialDigit // digits +} + +func NewDial(dialer Dialer) *Dial { + dial := &Dial{dialer: dialer} + dial.digits = make([]DialDigit, 10) + + for i := range dial.digits { + j := i + dial.digits[i].Value = strconv.Itoa(i) + dial.digits[i].ControlClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) { + dial.userTyped(ctx, j) + }) + dial.AddChild(&dial.digits[i]) + } + return dial +} + +func (d *Dial) userTyped(ctx ui.Context, i int) { + d.digits[i].Blink() + digit := strconv.Itoa(i) + if len(d.typing) == 0 || digit != d.typing { + d.typing = digit + d.digitCount = 1 + } else { + d.digitCount++ + } + + if !d.dialer.CanUserType(i) { + d.typing = "" + d.digitCount = 0 + d.dialer.UserGaveWrongInput() + } else if d.digitCount == i || d.digitCount == 10 { + d.typing = "" + d.digitCount = 0 + d.dialer.UserTyped(ctx, i) + } +} + +func (d *Dial) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) { + d.ControlBase.Arrange(ctx, bounds, offset, parent) + + center := bounds.Center() + size := bounds.Size() + + distance := size.Y * .3 + for i := range d.digits { + angle := (float32((10-i)%10)*0.16 + .2) * math.Pi + pos := geom.PtF32(distance*geom.Cos32(angle), .8*distance*geom.Sin32(angle)) + digitCenter := center.Add(pos) + d.digits[i].Arrange(ctx, geom.RectRelF32(digitCenter.X-24, digitCenter.Y-24, 48, 48), offset, d) + } +} + +func (d *Dial) DesiredSize(ctx ui.Context, size geom.PointF32) geom.PointF32 { + return geom.PtF32(size.X, geom.NaN32()) +} + +func (d *Dial) Handle(ctx ui.Context, event ui.Event) bool { + if d.ContainerBase.Handle(ctx, event) { + return true + } + switch e := event.(type) { + case *ui.KeyDownEvent: + switch e.Key { + case ui.Key0: + d.userTyped(ctx, 0) + case ui.KeyPad0: + d.userTyped(ctx, 0) + case ui.Key1: + d.userTyped(ctx, 1) + case ui.KeyPad1: + d.userTyped(ctx, 1) + case ui.Key2: + d.userTyped(ctx, 2) + case ui.KeyPad2: + d.userTyped(ctx, 2) + case ui.Key3: + d.userTyped(ctx, 3) + case ui.KeyPad3: + d.userTyped(ctx, 3) + case ui.Key4: + d.userTyped(ctx, 4) + case ui.KeyPad4: + d.userTyped(ctx, 4) + case ui.Key5: + d.userTyped(ctx, 5) + case ui.KeyPad5: + d.userTyped(ctx, 5) + case ui.Key6: + d.userTyped(ctx, 6) + case ui.KeyPad6: + d.userTyped(ctx, 6) + case ui.Key7: + d.userTyped(ctx, 7) + case ui.KeyPad7: + d.userTyped(ctx, 7) + case ui.Key8: + d.userTyped(ctx, 8) + case ui.KeyPad8: + d.userTyped(ctx, 8) + case ui.Key9: + d.userTyped(ctx, 9) + case ui.KeyPad9: + d.userTyped(ctx, 9) + } + } + return false +} + +func (d *Dial) Reset() { + d.typing = "" + d.digitCount = 0 +} + +func (d *Dial) Tick() { + for i := range d.digits { + d.digits[i].Tick() + } +} diff --git a/dialdigit.go b/dialdigit.go new file mode 100644 index 0000000..6a8c23a --- /dev/null +++ b/dialdigit.go @@ -0,0 +1,34 @@ +package tins2020 + +import ( + "opslag.de/schobers/geom" + "opslag.de/schobers/zntg" + "opslag.de/schobers/zntg/ui" +) + +type DialDigit struct { + ui.ControlBase + + Value string + + highlight int +} + +func (d *DialDigit) Blink() { + d.highlight = 4 +} + +func (d *DialDigit) Render(ctx ui.Context) { + color := zntg.MustHexColor(`#FFFFFF`) + if d.highlight > 0 { + color = zntg.MustHexColor(`#15569F`) + } + bounds := d.Bounds() + ctx.Fonts().TextAlign("title", geom.PtF32(bounds.Center().X, bounds.Min.Y), color, d.Value, ui.AlignCenter) +} + +func (d *DialDigit) Tick() { + if d.highlight > 0 { + d.highlight-- + } +} diff --git a/dialogs.go b/dialogs.go index bcd8e0c..e52bdad 100644 --- a/dialogs.go +++ b/dialogs.go @@ -5,7 +5,7 @@ import ( ) type Dialogs struct { - ui.OverlayProxy + ui.Proxy intro ui.Overlay research ui.Overlay @@ -21,7 +21,7 @@ const dialogsOverlayName = "dialogs" func NewDialogs(game *Game) *Dialogs { intro := NewIntro() research := NewResearch(game) - settings := NewLargeDialog("settings", &ui.Label{}) + settings := NewLargeDialog("Settings", &ui.Label{}) dialogs := &Dialogs{ intro: intro, @@ -46,9 +46,9 @@ func (d *Dialogs) Init(ctx ui.Context) { func (d *Dialogs) showDialog(ctx ui.Context, control ui.Control) { if control == nil { - d.Content = d.nothing ctx.Overlays().Hide(dialogsOverlayName) d.closed.Notify(ctx, control) + d.Content = d.nothing } else { d.Content = control ctx.Overlays().Show(dialogsOverlayName) @@ -63,10 +63,18 @@ func (d *Dialogs) Close(ctx ui.Context) { func (d *Dialogs) DialogClosed() ui.EventHandler { return &d.closed } func (d *Dialogs) DialogOpened() ui.EventHandler { return &d.opened } +func (d *Dialogs) Hidden() { + d.Proxy.Hidden() +} + func (d *Dialogs) ShowIntro(ctx ui.Context) { d.showDialog(ctx, d.intro) } +func (d *Dialogs) Shown() { + d.Proxy.Shown() +} + func (d *Dialogs) ShowResearch(ctx ui.Context) { d.showDialog(ctx, d.research) } diff --git a/largedialog.go b/largedialog.go index 27dba21..fe82abb 100644 --- a/largedialog.go +++ b/largedialog.go @@ -12,7 +12,7 @@ type LargeDialog struct { ui.StackPanel titleBar *LargeDialogTitleBar - content ui.OverlayProxy + content ui.Proxy closeRequested ui.Events } diff --git a/research.go b/research.go index 488a3d0..428d756 100644 --- a/research.go +++ b/research.go @@ -2,7 +2,6 @@ package tins2020 import ( "fmt" - "math" "math/rand" "strconv" "strings" @@ -15,185 +14,68 @@ import ( ) type Research struct { - ui.ContainerBase + ui.StackPanel game *Game botanist Specialist farmer Specialist - typing string - digitCount int - - close func() description ui.Paragraph specialists ui.Paragraph + dial *Dial input ui.Label - digits []Digit - animate zntg.Animation + + animate zntg.Animation + closeRequested ui.Events +} + +type Dialer interface { + CanUserType(int) bool + UserGaveWrongInput() + UserTyped(ui.Context, int) } func NewResearch(game *Game) *LargeDialog { research := &Research{game: game} research.animate.Interval = 20 * time.Millisecond - - research.Children = []ui.Control{&research.description, &research.specialists, &research.input} + research.animate.Start() research.description.Text = "Call a specialist to conduct research with." - research.digits = make([]Digit, 10) - for i := range research.digits { - j := i - research.digits[i].Value = strconv.Itoa(i) - research.digits[i].ControlClicked().AddHandler(func(ctx ui.Context, _ ui.ControlClickedArgs) { - research.userTyped(ctx, j) - }) - research.AddChild(&research.digits[i]) - } - dialog := NewLargeDialog("Research", research) - // dialog.OnShow().RegisterItf(func(state interface{}) { - // research.onShow(state.(ui.Context)) - // }) - // research.close = func() { dialog.CloseDialog() } + research.dial = NewDial(research) + research.Children = []ui.Control{&research.description, &research.specialists, research.dial, &research.input} + + dialog := NewLargeDialog("Research", ui.Stretch(research)) + research.closeRequested.AddHandlerEmpty(func(ctx ui.Context) { dialog.closeRequested.Notify(ctx, nil) }) return dialog } -type Digit struct { - ui.ControlBase - - Value string - - highlight int -} - -func (d *Digit) Blink() { - d.highlight = 4 -} - -func (d *Digit) Render(ctx ui.Context) { - color := zntg.MustHexColor(`#FFFFFF`) - if d.highlight > 0 { - color = zntg.MustHexColor(`#15569F`) - } - bounds := d.Bounds() - ctx.Fonts().TextAlign("title", geom.PtF32(bounds.Center().X, bounds.Min.Y), color, d.Value, ui.AlignCenter) -} - -func (d *Digit) Tick() { - if d.highlight > 0 { - d.highlight-- - } -} - type Specialist struct { Cost int Number string } func (r *Research) Arrange(ctx ui.Context, bounds geom.RectangleF32, offset geom.PointF32, parent ui.Control) { - r.ContainerBase.Arrange(ctx, bounds, offset, parent) - - size := bounds.Size() - r.specialists.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.Y+40, size.X, size.Y-40), offset, r) - r.input.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.X+size.Y-48, size.X, 24), offset, r) r.input.TextAlignment = ui.AlignCenter + r.StackPanel.Arrange(ctx, bounds, offset, parent) - center := bounds.Center() - - distance := size.Y * .3 - for i := range r.digits { - angle := (float32((10-i)%10)*0.16 + .2) * math.Pi - pos := geom.PtF32(distance*geom.Cos32(angle), .8*distance*geom.Sin32(angle)) - digitCenter := center.Add(pos) - r.digits[i].Arrange(ctx, geom.RectRelF32(digitCenter.X-24, digitCenter.Y-24, 48, 48), offset, r) - } + // size := bounds.Size() + // r.specialists.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.Y+40, size.X, size.Y-40), offset, r) + // r.input.Arrange(ctx, geom.RectRelF32(bounds.Min.X, bounds.Min.X+size.Y-48, size.X, 24), offset, r) } -func (r *Research) userTyped(ctx ui.Context, i int) { - r.digits[i].Blink() - digit := strconv.Itoa(i) - if len(r.typing) == 0 || digit != r.typing { - r.typing = digit - r.digitCount = 1 - } else { - r.digitCount++ - } - - if !strings.HasPrefix(r.botanist.Number, r.input.Text+r.typing) { - r.input.Text = "" - r.typing = "" - r.digitCount = 0 - } else if r.digitCount == i || r.digitCount == 10 { - r.input.Text += digit - r.typing = "" - r.digitCount = 0 - - if r.input.Text == r.botanist.Number { - r.game.UnlockNextFlower(ctx) - r.close() - r.input.Text = "" - } - } +func (r *Research) CanUserType(digit int) bool { + typing := strconv.Itoa(digit) + return strings.HasPrefix(r.botanist.Number, r.input.Text+typing) } -func (r *Research) Handle(ctx ui.Context, event ui.Event) bool { - if r.ContainerBase.Handle(ctx, event) { - return true - } - switch e := event.(type) { - case *ui.KeyDownEvent: - switch e.Key { - case ui.Key0: - r.userTyped(ctx, 0) - case ui.KeyPad0: - r.userTyped(ctx, 0) - case ui.Key1: - r.userTyped(ctx, 1) - case ui.KeyPad1: - r.userTyped(ctx, 1) - case ui.Key2: - r.userTyped(ctx, 2) - case ui.KeyPad2: - r.userTyped(ctx, 2) - case ui.Key3: - r.userTyped(ctx, 3) - case ui.KeyPad3: - r.userTyped(ctx, 3) - case ui.Key4: - r.userTyped(ctx, 4) - case ui.KeyPad4: - r.userTyped(ctx, 4) - case ui.Key5: - r.userTyped(ctx, 5) - case ui.KeyPad5: - r.userTyped(ctx, 5) - case ui.Key6: - r.userTyped(ctx, 6) - case ui.KeyPad6: - r.userTyped(ctx, 6) - case ui.Key7: - r.userTyped(ctx, 7) - case ui.KeyPad7: - r.userTyped(ctx, 7) - case ui.Key8: - r.userTyped(ctx, 8) - case ui.KeyPad8: - r.userTyped(ctx, 8) - case ui.Key9: - r.userTyped(ctx, 9) - case ui.KeyPad9: - r.userTyped(ctx, 9) - } - } - return false -} +func (r *Research) Hidden() {} func (r *Research) Render(ctx ui.Context) { - for i := range r.digits { - r.digits[i].Tick() - } - r.ContainerBase.Render(ctx) + r.animate.AnimateFn(r.dial.Tick) + r.StackPanel.Render(ctx) } -func (r *Research) onShow(ctx ui.Context) { +func (r *Research) Shown() { generateNumber := func() string { var number string for i := 0; i < 3; i++ { @@ -201,8 +83,9 @@ func (r *Research) onShow(ctx ui.Context) { } return number } - r.digitCount = 0 + r.input.Text = "" + r.dial.Reset() var specialists string defer func() { @@ -226,3 +109,16 @@ func (r *Research) onShow(ctx ui.Context) { specialists += fmt.Sprintf("Botanist: no. %s (unlocks next flower; $ %d)\n", r.botanist.Number, r.botanist.Cost) specialists += "Farmer: no. **unavailable** (fertilizes land; $ ---)\n" } + +func (r *Research) UserGaveWrongInput() { + r.input.Text = "" +} + +func (r *Research) UserTyped(ctx ui.Context, digit int) { + r.input.Text += strconv.Itoa(digit) + if r.input.Text == r.botanist.Number { + r.game.UnlockNextFlower(ctx) + r.input.Text = "" + r.closeRequested.Notify(ctx, nil) + } +}