236 lines
4.8 KiB
Go
236 lines
4.8 KiB
Go
package modes
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
|
|
"kidskeyboard/internal/ui"
|
|
)
|
|
|
|
type CalculatorMode struct {
|
|
ctx Context
|
|
display string
|
|
acc float64
|
|
op string
|
|
newEntry bool
|
|
err bool
|
|
}
|
|
|
|
func NewCalculatorMode(ctx Context) *CalculatorMode {
|
|
return &CalculatorMode{ctx: ctx, display: "0", newEntry: true}
|
|
}
|
|
|
|
func (m *CalculatorMode) Name() string { return "CTRL+F4 Calculator" }
|
|
func (m *CalculatorMode) OnEnter() {}
|
|
func (m *CalculatorMode) OnLeave() {}
|
|
func (m *CalculatorMode) Update() {}
|
|
|
|
func (m *CalculatorMode) HandleInput() {
|
|
handled := false
|
|
valid := false
|
|
chars := ebiten.AppendInputChars(nil)
|
|
|
|
for _, r := range chars {
|
|
handled = true
|
|
if m.handleChar(r) {
|
|
valid = true
|
|
}
|
|
}
|
|
|
|
for _, key := range justPressedKeys() {
|
|
switch {
|
|
case key == ebiten.KeyDelete:
|
|
handled = true
|
|
m.clear()
|
|
valid = true
|
|
case key == ebiten.KeyBackspace:
|
|
handled = true
|
|
m.backspace()
|
|
valid = true
|
|
case key == ebiten.KeyEnter || key == ebiten.KeyNumpadEnter:
|
|
handled = true
|
|
m.equals()
|
|
valid = true
|
|
case isNumpadOperatorKey(key) && len(chars) == 0:
|
|
handled = true
|
|
m.operator(operatorForKey(key))
|
|
valid = true
|
|
default:
|
|
if childKey(key) && len(chars) == 0 {
|
|
handled = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if valid {
|
|
m.ctx.Audio.PlayBeep()
|
|
} else if handled {
|
|
m.ctx.Audio.PlayError()
|
|
}
|
|
}
|
|
|
|
func (m *CalculatorMode) handleChar(r rune) bool {
|
|
switch {
|
|
case r >= '0' && r <= '9':
|
|
m.digit(string(r))
|
|
return true
|
|
case r == '+' || r == '-' || r == '*' || r == '/':
|
|
m.operator(string(r))
|
|
return true
|
|
case r == 'c' || r == 'C':
|
|
m.clear()
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (m *CalculatorMode) Draw(screen *ebiten.Image) {
|
|
screen.Fill(color.Black)
|
|
w, h := screen.Bounds().Dx(), screen.Bounds().Dy()
|
|
ui.Text(screen, "CTRL+F4", 20, 20, color.White, 2)
|
|
|
|
panelW := float32(min(float64(w)*0.78, 620))
|
|
keySize := panelW / 4
|
|
startX := (float32(w) - panelW) / 2
|
|
startY := float32(h)/2 - keySize*1.9
|
|
displayH := keySize * 0.72
|
|
|
|
ui.Rect(screen, startX, startY-displayH-16, panelW, displayH, color.NRGBA{R: 20, G: 20, B: 20, A: 255})
|
|
ui.RectOutline(screen, startX, startY-displayH-16, panelW, displayH, 2, color.White)
|
|
scale := 4.0
|
|
if len(m.display) > 12 {
|
|
scale = 3
|
|
}
|
|
ui.CenteredText(screen, m.display, int(startX+panelW/2), int(startY-displayH/2-16), color.White, scale)
|
|
|
|
keys := [][]string{
|
|
{"7", "8", "9", "/"},
|
|
{"4", "5", "6", "*"},
|
|
{"1", "2", "3", "-"},
|
|
{"C", "0", "=", "+"},
|
|
}
|
|
for r, row := range keys {
|
|
for c, label := range row {
|
|
x := startX + float32(c)*keySize
|
|
y := startY + float32(r)*keySize
|
|
ui.Rect(screen, x+4, y+4, keySize-8, keySize-8, color.NRGBA{R: 28, G: 28, B: 28, A: 255})
|
|
ui.RectOutline(screen, x+4, y+4, keySize-8, keySize-8, 2, color.White)
|
|
ui.CenteredText(screen, label, int(x+keySize/2), int(y+keySize/2), color.White, 4)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *CalculatorMode) digit(d string) {
|
|
if m.err || m.newEntry || m.display == "0" {
|
|
m.display = d
|
|
m.newEntry = false
|
|
m.err = false
|
|
return
|
|
}
|
|
if len(m.display) < 16 {
|
|
m.display += d
|
|
}
|
|
}
|
|
|
|
func (m *CalculatorMode) operator(op string) {
|
|
if m.err {
|
|
return
|
|
}
|
|
if m.op != "" && !m.newEntry {
|
|
m.equals()
|
|
}
|
|
m.acc = m.value()
|
|
m.op = op
|
|
m.newEntry = true
|
|
}
|
|
|
|
func (m *CalculatorMode) equals() {
|
|
if m.err || m.op == "" {
|
|
m.newEntry = true
|
|
return
|
|
}
|
|
right := m.value()
|
|
var result float64
|
|
switch m.op {
|
|
case "+":
|
|
result = m.acc + right
|
|
case "-":
|
|
result = m.acc - right
|
|
case "*":
|
|
result = m.acc * right
|
|
case "/":
|
|
if right == 0 {
|
|
m.display = "DIV 0"
|
|
m.err = true
|
|
m.op = ""
|
|
m.newEntry = true
|
|
return
|
|
}
|
|
result = m.acc / right
|
|
}
|
|
m.display = formatNumber(result)
|
|
m.acc = result
|
|
m.op = ""
|
|
m.newEntry = true
|
|
}
|
|
|
|
func (m *CalculatorMode) backspace() {
|
|
if m.err || m.newEntry || len(m.display) <= 1 {
|
|
m.display = "0"
|
|
m.err = false
|
|
m.newEntry = true
|
|
return
|
|
}
|
|
m.display = m.display[:len(m.display)-1]
|
|
}
|
|
|
|
func (m *CalculatorMode) clear() {
|
|
m.display = "0"
|
|
m.acc = 0
|
|
m.op = ""
|
|
m.err = false
|
|
m.newEntry = true
|
|
}
|
|
|
|
func (m *CalculatorMode) value() float64 {
|
|
v, _ := strconv.ParseFloat(m.display, 64)
|
|
return v
|
|
}
|
|
|
|
func formatNumber(v float64) string {
|
|
if math.Abs(v-math.Round(v)) < 0.0000001 {
|
|
return fmt.Sprintf("%.0f", v)
|
|
}
|
|
return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.6f", v), "0"), ".")
|
|
}
|
|
|
|
func isNumpadOperatorKey(key ebiten.Key) bool {
|
|
switch key {
|
|
case ebiten.KeyNumpadAdd, ebiten.KeyNumpadSubtract, ebiten.KeyNumpadMultiply, ebiten.KeyNumpadDivide:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func operatorForKey(key ebiten.Key) string {
|
|
switch key {
|
|
case ebiten.KeyNumpadAdd:
|
|
return "+"
|
|
case ebiten.KeyNumpadSubtract:
|
|
return "-"
|
|
case ebiten.KeyNumpadMultiply:
|
|
return "*"
|
|
case ebiten.KeyNumpadDivide:
|
|
return "/"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|