From bd857a4e52780aff1259cf48ec9046bf4e08db5c Mon Sep 17 00:00:00 2001 From: Gyuri Horak Date: Mon, 3 Nov 2025 18:41:28 +0100 Subject: [PATCH] initial version [by claude] --- .claude/settings.local.json | 10 ++ .gitignore | 69 ++++++++++ PLAN-metric-watchface.md | 131 +++++++++++++++++++ README.md | 130 +++++++++++++++++++ manifest.xml | 65 ++++++++++ monkey.jungle | 1 + resources/drawables/drawables.xml | 3 + resources/drawables/launcher_icon.png | Bin 0 -> 1292 bytes resources/strings/strings.xml | 3 + source/MetricWatchFaceApp.mc | 23 ++++ source/MetricWatchFaceView.mc | 174 ++++++++++++++++++++++++++ 11 files changed, 609 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 .gitignore create mode 100644 PLAN-metric-watchface.md create mode 100644 README.md create mode 100644 manifest.xml create mode 100644 monkey.jungle create mode 100644 resources/drawables/drawables.xml create mode 100644 resources/drawables/launcher_icon.png create mode 100644 resources/strings/strings.xml create mode 100644 source/MetricWatchFaceApp.mc create mode 100644 source/MetricWatchFaceView.mc diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..193fe94 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "WebSearch", + "WebFetch(domain:github.com)" + ], + "deny": [], + "ask": [] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1aaf606 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Garmin Connect IQ Build Artifacts +*.prg +*.iq +bin/ +*.debug.xml + +# Developer Keys (IMPORTANT: Never commit these!) +*.der +*.pem +developer_key +developer_key.der + +# IDE Files +.vscode/ +.idea/ +*.iml +.settings/ +.project +.classpath + +# Eclipse/Garmin Plugin +.externalToolBuilders/ + +# Build directories +build/ +target/ +out/ + +# Temporary files +*.tmp +*.bak +*.swp +*~ +.DS_Store +Thumbs.db + +# Logs +*.log + +# Generated resources +gen/ + +# Simulator data +.simulator/ + +# Barrel files (if not distributing) +barrels/ + +# Mac OS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ + +# Linux +*~ +.directory + +# Compiled source +*.class +*.dll +*.exe +*.o +*.so diff --git a/PLAN-metric-watchface.md b/PLAN-metric-watchface.md new file mode 100644 index 0000000..14cfacb --- /dev/null +++ b/PLAN-metric-watchface.md @@ -0,0 +1,131 @@ +# Decimal Day Progress Watchface - Implementation Plan + +## Concept Overview + +Create a joke/novelty Garmin watchface that displays time as a decimal day progress meter instead of traditional 12/24 hour format. + +### Key Features +- **10 equal divisions** on the watchface (instead of 12 hours) +- **Single hand** showing day progress as a decimal (0-10) +- **Analog display** with tick marks at each decimal position +- **Optional digital readout** showing exact decimal time + +### Time Mapping +- Midnight (00:00) → 0.0 +- 6:00 AM → 2.5 +- Noon (12:00) → 5.0 +- 6:00 PM → 7.5 +- Midnight (24:00) → 10.0 (wraps to 0) + +## Technical Implementation + +### Project Structure +``` +MetricWatchFace/ +├── manifest.xml # App metadata and device compatibility +├── monkey.jungle # Build configuration +├── resources/ +│ ├── drawables/ +│ │ └── drawables.xml # Layout definitions +│ ├── strings/ +│ │ └── strings.xml # Localization +│ └── settings/ +│ └── settings.xml # User settings (if needed) +└── source/ + ├── MetricWatchFaceApp.mc # Application entry point + └── MetricWatchFaceView.mc # Main watchface view logic +``` + +### Core Calculation + +**Decimal Time Formula:** +``` +decimalTime = (currentHour + currentMinute/60.0 + currentSecond/3600.0) / 24.0 * 10.0 +``` + +**Hand Angle Calculation:** +``` +handAngle = (decimalTime / 10.0) * 360.0 - 90.0 // -90 to start at top (12 o'clock position) +``` + +### Drawing Components + +1. **Watchface Circle** + - Use `drawCircle()` or `drawArc()` for outline + - Draw 10 tick marks at 36° intervals (360°/10) + +2. **Decimal Markers** + - Place numbers 0-9 around the perimeter + - Position 0 at top (traditional 12 o'clock position) + - Position 5 at bottom (traditional 6 o'clock position) + +3. **Progress Hand** + - Use `fillPolygon()` to draw a triangle/arrow hand + - Calculate hand coordinates using trigonometry + - Rotate based on current decimal time + +4. **Digital Display** (optional) + - Show exact decimal time (e.g., "3.47") + - Position at center or bottom of face + +### Key Methods + +**MetricWatchFaceView.mc:** +- `initialize()` - Set up initial state +- `onUpdate(dc)` - Main rendering method (called periodically) +- `computeDecimalTime()` - Calculate current decimal time +- `drawWatchFace(dc)` - Draw the analog face and markers +- `drawHand(dc, decimalTime)` - Draw the progress hand +- `drawDigitalTime(dc, decimalTime)` - Optional digital display + +### Device Compatibility + +Target devices: +- Fenix series (5, 6, 7) +- Forerunner series (245, 645, 945) +- Vivoactive series (3, 4) +- Other modern Garmin watches with Connect IQ support + +Considerations: +- Handle different screen sizes/resolutions +- Support both round and semi-round displays +- Test with MIP and AMOLED displays + +## Implementation Steps + +1. **Project Setup** + - Create manifest.xml with app metadata + - Set up basic directory structure + - Create application entry point + +2. **Core Logic** + - Implement decimal time calculation + - Add time update handling + - Create view class structure + +3. **Rendering** + - Draw watchface circle and divisions + - Add decimal markers (0-9) + - Implement hand rotation logic + - Add digital display + +4. **Testing & Refinement** + - Test in simulator + - Verify calculations at key times + - Optimize drawing performance + - Test on actual device if available + +## Future Enhancements (Optional) + +- Add date display +- Include battery indicator +- Show step count or other metrics +- Customizable colors via settings +- Alternative hand styles +- Animation effects + +## References + +- Garmin Connect IQ SDK Documentation +- Swiss Railway Clock example (analog implementation) +- Monkey C API reference for graphics primitives diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f5f962 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Metric Day Progress Watchface + +A novelty Garmin watchface that displays time as decimal day progress instead of traditional 12/24 hour format! + +## Concept + +Instead of the traditional 12-hour clock, this watchface divides the day into **10 equal parts** and displays your progress through the day as a decimal number from 0 to 10. + +### Time Conversions + +- **Midnight (00:00)** → 0.0 +- **6:00 AM** → 2.5 +- **Noon (12:00)** → 5.0 +- **6:00 PM** → 7.5 +- **End of day (24:00)** → 10.0 + +## Features + +- Analog watchface with 10 divisions (0-9) +- Rotating red hand showing current day progress +- Digital decimal time display +- Clean, minimalist design +- Supports most modern Garmin devices + +## Building the Watchface + +### Prerequisites + +1. **Garmin Connect IQ SDK** - Download from [developer.garmin.com](https://developer.garmin.com/connect-iq/sdk/) +2. **Java Development Kit (JDK)** - OpenJDK recommended +3. **Visual Studio Code** (optional but recommended) +4. **Garmin Monkey C Extension** for VS Code (optional) + +### Build Methods + +#### Method 1: Using Visual Studio Code + +1. Open this folder in VS Code +2. Ensure the Monkey C extension is installed +3. Press the play button in the toolbar +4. Select your target device from the simulator + +#### Method 2: Using Command Line + +If you have the SDK installed and `$SDK_PATH` set: + +```bash +# Build for simulator +$SDK_PATH/bin/monkeyc \ + -o MetricWatchFace.prg \ + -f monkey.jungle \ + -y $SDK_PATH/bin/developer_key + +# Run in simulator +$SDK_PATH/bin/connectiq +``` + +#### Method 3: Using monkeybrains (if available) + +```bash +monkeybrains build +monkeybrains run +``` + +### Deploying to a Real Device + +1. Build the `.prg` file using one of the methods above +2. Connect your Garmin device via USB +3. Copy the `.prg` file to the `GARMIN/APPS` folder on your device +4. Safely disconnect the device +5. The watchface should now be available in your watchface selection menu + +## Project Structure + +``` +MetricWatchFace/ +├── manifest.xml # App configuration +├── monkey.jungle # Build configuration +├── source/ +│ ├── MetricWatchFaceApp.mc # Application entry point +│ └── MetricWatchFaceView.mc # Main watchface logic +└── resources/ + ├── drawables/ + │ ├── drawables.xml # Drawable resources + │ └── launcher_icon.png # App icon (placeholder) + └── strings/ + └── strings.xml # String resources +``` + +## How It Works + +The watchface calculates decimal time using the formula: + +``` +decimalTime = (hours + minutes/60 + seconds/3600) / 24 * 10 +``` + +This value (0.0 to 10.0) is then displayed: +- **Graphically** via a rotating red hand on an analog face +- **Digitally** as a decimal number at the bottom of the screen + +## Supported Devices + +This watchface supports a wide range of Garmin devices including: +- Fenix series (5, 5 Plus, 6, 7) +- Forerunner series (245, 645, 945, 955) +- Vivoactive series (3, 4) +- Venu series (1, 2, 2 Plus) + +See `manifest.xml` for the complete list. + +## Notes + +- The launcher icon is currently a placeholder - you can replace it with your own PNG image +- The watchface uses a simple black background with white/red elements +- Updates occur every second when active to show smooth hand movement + +## Future Enhancements + +Potential improvements: +- Add date display +- Battery indicator +- Step count or other fitness metrics +- Customizable colors via settings +- Different hand styles +- AMOLED-optimized always-on mode + +## License + +This is a fun/joke project - feel free to use and modify as you wish! diff --git a/manifest.xml b/manifest.xml new file mode 100644 index 0000000..31153b9 --- /dev/null +++ b/manifest.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eng + + + + \ No newline at end of file diff --git a/monkey.jungle b/monkey.jungle new file mode 100644 index 0000000..87796c7 --- /dev/null +++ b/monkey.jungle @@ -0,0 +1 @@ +project.manifest = manifest.xml diff --git a/resources/drawables/drawables.xml b/resources/drawables/drawables.xml new file mode 100644 index 0000000..a22c33c --- /dev/null +++ b/resources/drawables/drawables.xml @@ -0,0 +1,3 @@ + + + diff --git a/resources/drawables/launcher_icon.png b/resources/drawables/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d3594e7cb975929fe36f50e5bffde505262c0901 GIT binary patch literal 1292 zcmV+n1@roeP)@WZT1er-h zK~zY`<(A)TmSq^nKiB>IdiTD2w>fi5H+`GV>1^WEG${}%gHBRYgw7JFgHHMnf)0XC zDu^JYjx2~muu?oJVwhw>YA7}ln>v=;+}v#M-k;C=-1ptVv!AZHNrDGn@Eklm&wYQd zU*GR_$&$=Q%OfzwBeF*Z?EbIQ)`B&Nv4F)QpaevL$O8adi&R2T4%T(=3n-NkQ(KQs zrLkTBN`W;HmQlGZlF!BtD77%Ktx6HKZ9;NmID>uhMKetVDVH#{^;A~2618t3YH3F| zuD}LKq+G-q?#Jor#=CY2x2GE^jDs^b2&by9@!iQ6-Lj7Hx8KL4>+rhIBVl*bEu(P;CXR9tpQ{3FZ@in zWhbRwdxcYT+gdtJeCQNz>L=}QFr=||yUz!x64c^6HQ6sl8%QKjU6;pLy zFk*>9bYnBNx)$&H6{K8RyuNro?)3>u8AG(P6_m5sR2yuv8tVth=nz;IRHa!_XGbB5 z;(O9{i;oxtjE)bwJ_vwaw4{pT#b5&H$^#4u$3fj4L5g{FQ%mfKT_8hVfUQb`(L@bR z;0K@_)Sba8HCzDLV*CKN?;0w57n5m4H#E(k>Zn4LX=Ci2BNPt4g-zAq4));;^g$)W z?YRPrrjzI4cArPaMj)4^xbH9|l5=$&!1d6}S7B2%gxhve+;a$1TTk-K52^nAJ)~Ti za(Y^;MKH^j6F$2e=XM|drQeWp5kw(j8PSHQWj%W3YH&TAz8+LD&qUGV3ys0Obput* z5oOwl)^*06-PeQLdv#W0&JTQX8kh-Mm?u`C8=HyRH)EDQi8DGBi-<9ZHL=#a9`0Y4 z5v_@uo+8@t4AyaQ|GqJ6=**VP$@!@iRs@;RoiB>-V`oLCQmGr8NqzAViJy)G#NaQ#h8n$1;`GV5B(s<^aFf(mAEQQx zky0^UWA0f5Y$8c;`ULKk3q)%>&<)MlnkC?R5LJ-e7;gW~_}y9(UEhOpsB~<{xic^& zw=!$JH3kVwvB-P=f{G?#Ii_5?4qP9i3M!ukqp?BNM8j#UpTI0_#Odz^N5wPXq3qSx z#*fH + Metric Day + diff --git a/source/MetricWatchFaceApp.mc b/source/MetricWatchFaceApp.mc new file mode 100644 index 0000000..9977645 --- /dev/null +++ b/source/MetricWatchFaceApp.mc @@ -0,0 +1,23 @@ +using Toybox.Application; +using Toybox.WatchUi; + +class MetricWatchFaceApp extends Application.AppBase { + + function initialize() { + AppBase.initialize(); + } + + // onStart() is called on application start up + function onStart(state) { + } + + // onStop() is called when your application is exiting + function onStop(state) { + } + + // Return the initial view of your application here + function getInitialView() { + return [ new MetricWatchFaceView() ]; + } + +} diff --git a/source/MetricWatchFaceView.mc b/source/MetricWatchFaceView.mc new file mode 100644 index 0000000..ada0df7 --- /dev/null +++ b/source/MetricWatchFaceView.mc @@ -0,0 +1,174 @@ +using Toybox.WatchUi; +using Toybox.Graphics; +using Toybox.System; +using Toybox.Lang; +using Toybox.Time; +using Toybox.Time.Gregorian; + +class MetricWatchFaceView extends WatchUi.WatchFace { + + function initialize() { + WatchFace.initialize(); + } + + // Load your resources here + function onLayout(dc) { + // We draw everything programmatically, no layout needed + } + + // Called when this View is brought to the foreground. Restore + // the state of this View and prepare it to be shown. This includes + // loading resources into memory. + function onShow() { + } + + // Update the view + function onUpdate(dc) { + // Get the current time + var clockTime = System.getClockTime(); + var decimalTime = computeDecimalTime(clockTime); + + // Clear the screen + dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK); + dc.clear(); + + // Draw the watchface + drawWatchFace(dc); + + // Draw the hand + drawHand(dc, decimalTime); + + // Draw digital time display + drawDigitalTime(dc, decimalTime); + } + + // Called when this View is removed from the screen. Save the + // state of this View here. This includes freeing resources from + // memory. + function onHide() { + } + + // The user has just looked at their watch. Timers and animations may be started here. + function onExitSleep() { + } + + // Terminate any active timers and prepare for slow updates. + function onEnterSleep() { + } + + // Compute decimal time from current clock time + // Returns a value from 0.0 to 10.0 representing the day's progress + function computeDecimalTime(clockTime) { + var hour = clockTime.hour; + var min = clockTime.min; + var sec = clockTime.sec; + + // Convert to decimal: (hours + minutes/60 + seconds/3600) / 24 * 10 + var decimalTime = (hour + (min / 60.0) + (sec / 3600.0)) / 24.0 * 10.0; + + return decimalTime; + } + + // Draw the watchface circle, tick marks, and numbers + function drawWatchFace(dc) { + var width = dc.getWidth(); + var height = dc.getHeight(); + var centerX = width / 2; + var centerY = height / 2; + var radius = (width < height ? width : height) / 2 - 10; + + // Draw outer circle + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + dc.setPenWidth(3); + dc.drawCircle(centerX, centerY, radius); + + // Draw tick marks and numbers for each decimal position (0-9) + dc.setPenWidth(2); + for (var i = 0; i < 10; i++) { + var angle = (i / 10.0) * 360.0 - 90.0; // -90 to start at top + var angleRad = Math.toRadians(angle); + + // Calculate tick mark positions + var tickOuterX = centerX + radius * Math.cos(angleRad); + var tickOuterY = centerY + radius * Math.sin(angleRad); + var tickInnerX = centerX + (radius - 15) * Math.cos(angleRad); + var tickInnerY = centerY + (radius - 15) * Math.sin(angleRad); + + // Draw tick mark + dc.drawLine(tickOuterX, tickOuterY, tickInnerX, tickInnerY); + + // Draw number + var numberX = centerX + (radius - 35) * Math.cos(angleRad); + var numberY = centerY + (radius - 35) * Math.sin(angleRad); + + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + dc.drawText( + numberX, + numberY, + Graphics.FONT_MEDIUM, + i.toString(), + Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER + ); + } + + // Draw center dot + dc.fillCircle(centerX, centerY, 5); + } + + // Draw the hand pointing to current decimal time + function drawHand(dc, decimalTime) { + var width = dc.getWidth(); + var height = dc.getHeight(); + var centerX = width / 2; + var centerY = height / 2; + var radius = (width < height ? width : height) / 2 - 10; + + // Calculate hand angle (0-10 maps to 0-360 degrees, starting at top) + var angle = (decimalTime / 10.0) * 360.0 - 90.0; // -90 to start at top + var angleRad = Math.toRadians(angle); + + // Hand length (slightly shorter than radius to not overlap with numbers) + var handLength = radius - 50; + + // Calculate hand tip position + var handTipX = centerX + handLength * Math.cos(angleRad); + var handTipY = centerY + handLength * Math.sin(angleRad); + + // Calculate hand base positions (for a triangle/arrow shape) + var handWidth = 6; + var perpAngleRad1 = angleRad + Math.toRadians(90); + var perpAngleRad2 = angleRad - Math.toRadians(90); + + var baseX1 = centerX + handWidth * Math.cos(perpAngleRad1); + var baseY1 = centerY + handWidth * Math.sin(perpAngleRad1); + var baseX2 = centerX + handWidth * Math.cos(perpAngleRad2); + var baseY2 = centerY + handWidth * Math.sin(perpAngleRad2); + + // Draw the hand as a filled polygon + dc.setColor(Graphics.COLOR_RED, Graphics.COLOR_TRANSPARENT); + dc.fillPolygon([ + [handTipX, handTipY], + [baseX1, baseY1], + [baseX2, baseY2] + ]); + } + + // Draw digital display of decimal time + function drawDigitalTime(dc, decimalTime) { + var width = dc.getWidth(); + var height = dc.getHeight(); + + // Format decimal time to 2 decimal places + var timeString = decimalTime.format("%.2f"); + + // Draw at bottom center + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + dc.drawText( + width / 2, + height * 0.75, + Graphics.FONT_LARGE, + timeString, + Graphics.TEXT_JUSTIFY_CENTER | Graphics.TEXT_JUSTIFY_VCENTER + ); + } +}