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 "" } }