Calculator v1.6: round-screen keypad with arithmetic

- 19-key layout fitted to round display, centered focus, scrollable list
- full arithmetic: +-*/ %, decimals, +/- , AC, divide-by-zero, chained ops
- scroll-to-center focus with top/bottom spacers, high-contrast keys
- tests: arithmetic + geometry/contrast
This commit is contained in:
2026-09-09 15:09:31 +01:00
commit 176de6bc1f
12 changed files with 10698 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
<script>
export default {
onCreate() {
console.log('Hello Vela app created')
},
onDestroy() {
console.log('Hello Vela app destroyed')
}
}
</script>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
<rect width="192" height="192" rx="48" fill="#ff6900"/>
<text x="96" y="116" text-anchor="middle" font-family="DejaVu Sans, sans-serif" font-size="72" font-weight="700" fill="#ffffff">Hi</text>
</svg>

After

Width:  |  Height:  |  Size: 293 B

+27
View File
@@ -0,0 +1,27 @@
{
"package": "com.example.hellovela",
"name": "Calc",
"icon": "/common/icon.png",
"versionName": "1.6",
"versionCode": 7,
"minAPILevel": 1,
"deviceTypeList": [
"watch"
],
"config": {
"logLevel": "debug",
"designWidth": 480
},
"router": {
"entry": "pages/index",
"pages": {
"pages/index": {
"component": "index",
"path": "/"
}
}
},
"display": {
"backgroundColor": "#000000"
}
}
+108
View File
@@ -0,0 +1,108 @@
<template>
<div class="page">
<text class="display" style="font-size: {{displaySize}}px">{{ display }}</text>
<list class="keys" id="keys" onscroll="onScroll">
<list-item type="pad" class="pad top"></list-item>
<list-item type="key" class="item {{ $idx === focused ? 'focused' : '' }}" for="{{ keys }}" onclick="confirmKey">
<text class="keylabel" style="color: {{ $idx === focused ? '#000000' : '#ffffff' }}">{{ $item.label }}</text>
</list-item>
<list-item type="pad" class="pad bottom"></list-item>
</list>
</div>
</template>
<script>
export default {
private: {
display: '0',
focused: 0,
keys: []
},
computed: {
displaySize() {
return this.display.length > 9 ? 38 : this.display.length > 7 ? 46 : 52
}
},
onInit() {
const labels = ['AC','+/-','%','/','7','8','9','x','4','5','6','-','1','2','3','+','0','.','=']
this.keys = labels.map(l => ({ label: l }))
this.ITEM_H = 54
this.KEYS_H = 300
this.PAD = this.KEYS_H / 2 - this.ITEM_H / 2 // 123px spacers so first/last item can center
},
onScroll(e) {
// focused item = the one centered in the list's viewport (spacers included in offset)
const idx = Math.round((e.scrollY + this.KEYS_H / 2 - this.ITEM_H / 2 - this.PAD) / this.ITEM_H)
this.focused = Math.max(0, Math.min(this.keys.length - 1, idx))
},
confirmKey() {
if (!this.keys.length) return
this.press(this.keys[this.focused].label)
},
press(key) {
if (key === '+') { this.stored = Number(this.display); this.op = '+'; this.fresh = true }
else if (key === '-') { this.stored = Number(this.display); this.op = '-'; this.fresh = true }
else if (key === 'x') { this.stored = Number(this.display); this.op = 'x'; this.fresh = true }
else if (key === '/') { this.stored = Number(this.display); this.op = '/'; this.fresh = true }
else if (key === '=') {
const b = Number(this.display)
let r = 0
if (this.op === '+') r = this.stored + b
else if (this.op === '-') r = this.stored - b
else if (this.op === 'x') r = this.stored * b
else if (this.op === '/') r = b === 0 ? NaN : this.stored / b
this.display = Number.isFinite(r) ? String(Math.round(r * 1e9) / 1e9) : 'Error'
this.fresh = true
}
else if (key === 'AC') { this.display = '0'; this.stored = 0; this.op = ''; this.fresh = true }
else if (key === '+/-') { this.display = this.display.startsWith('-') ? this.display.slice(1) : '-' + this.display }
else if (key === '%') { this.display = String(Number(this.display) / 100) }
else if (key === '.') { if (this.fresh) { this.display = '0.'; this.fresh = false } else if (!this.display.includes('.')) this.display += '.' }
else {
if (this.fresh) { this.display = key; this.fresh = false }
else if (this.display.replace(/[-.]/g,'').length < 9) this.display += key
}
// keep the focused item visually centered after each press
const el = this.$element('keys')
el && el.scrollTo({
top: this.PAD + this.focused * this.ITEM_H - this.KEYS_H / 2 + this.ITEM_H / 2,
behavior: 'smooth'
})
},
onBackPress() {
this.confirmKey()
return true
}
}
</script>
<style>
.page {
width: 480px; height: 480px;
background-color: #000000;
flex-direction: column;
align-items: center;
justify-content: center;
}
.display {
width: 340px; height: 70px;
color: #ffffff;
text-align: center;
}
.keys {
width: 320px; height: 300px;
}
.item {
width: 300px; height: 54px;
justify-content: center; align-items: center;
border-radius: 27px;
background-color: #3a3a3e;
}
.focused {
background-color: #f3a633;
}
.pad {
height: 123px;
}
.keylabel {
font-size: 34px; text-align: center;
}
</style>