Files
KidsKeyboard/internal/modes/calculator.go
2026-05-22 13:00:28 +02:00

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