diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..93f9b29 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,153 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a novelty Garmin Connect IQ watchface that displays time as decimal day progress (French Revolutionary Time style) instead of traditional 12/24 hour format. The day is divided into 10 equal parts, with time displayed as a decimal from 0.0 to 10.0. + +**Key Concept**: The watchface converts standard time to decimal using the formula: +``` +decimalTime = (hours + minutes/60 + seconds/3600) / 24 * 10 +``` + +This means: +- Midnight (00:00) → 0.0 +- 6:00 AM → 2.5 +- Noon (12:00) → 5.0 +- 6:00 PM → 7.5 +- End of day → 10.0 + +## Development Commands + +### Building the Watchface + +The project uses the Garmin Connect IQ SDK. You need the SDK installed with `$SDK_PATH` environment variable set. + +**Build for simulator:** +```bash +$SDK_PATH/bin/monkeyc \ + -o bin/MetricWatchFace.prg \ + -f monkey.jungle \ + -y $SDK_PATH/bin/developer_key +``` + +**Run in simulator:** +```bash +$SDK_PATH/bin/connectiq +# Then load the generated .prg file in the simulator +``` + +**Build for specific device (example for Fenix 7):** +```bash +$SDK_PATH/bin/monkeyc \ + -o bin/MetricWatchFace.prg \ + -f monkey.jungle \ + -d fenix7 \ + -y $SDK_PATH/bin/developer_key +``` + +**Note**: The project uses `monkey.jungle` for build configuration, which references `manifest.xml` for project settings. + +## Architecture + +### Core Components + +**DecimalWatchFaceApp.mc** (`source/DecimalWatchFaceApp.mc:4-23`) +- Application entry point extending `Application.AppBase` +- Minimal setup - primarily returns the watchface view +- No complex application state management + +**DecimalWatchFaceView.mc** (`source/DecimalWatchFaceView.mc:8-201`) +- Main watchface implementation extending `WatchUi.WatchFace` +- Handles all rendering and time calculation logic +- Key methods: + - `onUpdate(dc)` - Main render loop, called every second when active + - `computeDecimalTime(clockTime)` - Converts standard time to decimal (0.0-10.0) + - `drawWatchFace(dc)` - Renders the analog face with 10 divisions, tick marks, and numbers + - `drawHand(dc, decimalTime)` - Draws both hour and minute hands + - `drawSingleHand(...)` - Helper for rendering individual hands as filled polygons + - `drawDigitalTime(dc, decimalTime)` - Displays decimal time as text + +### Rendering Architecture + +The watchface uses programmatic drawing (no XML layouts). All graphics are drawn using Monkey C Graphics API: + +1. **Watchface Circle**: 100 tick marks drawn in a loop (10 major at whole numbers 0-9, 90 minor at 0.1 intervals) +2. **Two Hands**: + - **Hour hand** (red, shorter, wider): Shows which decimal unit (0-9), completes one rotation per day + - **Minute hand** (white, longer, thinner): Shows fractional part within current decimal unit, completes 10 rotations per day +3. **Digital Display**: Shows exact decimal time at bottom (formatted to 2 decimal places) + +The hand rotation uses trigonometry with angles calculated from decimal time, offset by -90° to start at top (12 o'clock position). + +### Device Support + +The manifest (`manifest.xml`) targets 57 modern Garmin devices including: +- Fenix series (6, 7, 8, Epix 2) +- Forerunner series (255, 265, 645, 945, 955, 965, 970) +- Vivoactive series (3, 4, 5, 6) +- Venu series (2 Plus, 3, 4) + +All devices require Connect IQ API level 3.2.0 or higher. + +## Project Structure + +``` +MetricWatchFace/ +├── manifest.xml # App metadata, device compatibility +├── monkey.jungle # Build configuration +├── source/ +│ ├── DecimalWatchFaceApp.mc # Application entry point +│ └── DecimalWatchFaceView.mc # Main watchface view and rendering +└── resources/ + ├── drawables/ + │ ├── drawables.xml # Drawable resource definitions + │ └── launcher_icon.png # App icon (placeholder) + └── strings/ + └── strings.xml # Localized strings +``` + +## Important Implementation Details + +### Time Calculation Edge Cases +- The decimal time formula ensures smooth progression from 0.0 to 10.0 +- The fractional part extraction (`decimalTime - decimalTime.toNumber()`) is used for the minute hand +- Both hands update every second when the watchface is active + +### Drawing Performance +- The watchface redraws completely on each `onUpdate()` call +- 100 tick marks are drawn in every frame (major optimization opportunity) +- Hands are drawn as filled polygons using triangle geometry +- Center dot is redrawn after hands to ensure it's on top + +### Sleep Mode Handling +- `onEnterSleep()` and `onExitSleep()` are currently empty +- In sleep mode, the system reduces update frequency automatically +- Consider implementing reduced complexity rendering for sleep mode + +## Known Technical Debt + +From IMPROVEMENT_IDEAS.md: +- Launcher icon is currently a placeholder PNG +- No screen type optimization (MIP vs AMOLED) +- No low power mode implementation +- Hand polygon drawing could be optimized +- No error handling for edge cases +- No unit tests for time conversion + +## Monkey C Specifics + +**Language**: Monkey C (Garmin's proprietary language, similar to Java/C) +**Key APIs used**: +- `Toybox.WatchUi.WatchFace` - Base class for watchfaces +- `Toybox.Graphics` - Drawing primitives (drawLine, fillPolygon, drawText, etc.) +- `Toybox.System` - System functions (getClockTime) +- `Toybox.Time` / `Toybox.Time.Gregorian` - Time utilities +- `Toybox.Lang` / `Toybox.Math` - Language and math utilities + +**Common patterns**: +- Drawing context (`dc`) is passed to all render methods +- Colors use `Graphics.COLOR_*` constants +- Coordinates are in pixels, origin at top-left +- All trigonometry uses radians (convert with `Math.toRadians()`) diff --git a/IMPROVEMENT_IDEAS.md b/IMPROVEMENT_IDEAS.md index cd2c6da..4d7377b 100644 --- a/IMPROVEMENT_IDEAS.md +++ b/IMPROVEMENT_IDEAS.md @@ -8,7 +8,7 @@ This document contains potential enhancements and features to improve the decima ### 1. Better Graphics & Styling - **Proper launcher icon** - Replace placeholder with actual designed icon -- **Sub-divisions/minor tick marks** - Add marks at 0.5 intervals for finer precision +- **Sub-divisions/minor tick marks** - Add marks at 0.5 intervals for finer precision (/) - **Color themes** - Dark mode / Light mode - "Metric blue/orange" scheme diff --git a/source/DecimalWatchFaceView.mc b/source/DecimalWatchFaceView.mc index 17249b8..1c52591 100644 --- a/source/DecimalWatchFaceView.mc +++ b/source/DecimalWatchFaceView.mc @@ -7,6 +7,8 @@ using Toybox.Time.Gregorian; class DecimalWatchFaceView extends WatchUi.WatchFace { + var isHighPowerMode = true; // Track if we're in high power mode + function initialize() { WatchFace.initialize(); } @@ -50,10 +52,12 @@ class DecimalWatchFaceView extends WatchUi.WatchFace { // The user has just looked at their watch. Timers and animations may be started here. function onExitSleep() { + isHighPowerMode = true; } // Terminate any active timers and prepare for slow updates. function onEnterSleep() { + isHighPowerMode = false; } // Compute decimal time from current clock time @@ -124,7 +128,7 @@ class DecimalWatchFaceView extends WatchUi.WatchFace { dc.fillCircle(centerX, centerY, 5); } - // Draw both hour and minute hands for current decimal time + // Draw hour, minute, and second hands for current decimal time function drawHand(dc, decimalTime) { var width = dc.getWidth(); var height = dc.getHeight(); @@ -143,6 +147,17 @@ class DecimalWatchFaceView extends WatchUi.WatchFace { var minuteAngleRad = Math.toRadians(minuteAngle); var minuteHandLength = radius - 45; // Longer hand + // Calculate second hand (shows finer detail, 100 rotations per day) + // Only visible in high power mode + var secondFractionalPart = (decimalTime * 10.0) - (decimalTime * 10.0).toNumber(); + var secondAngle = secondFractionalPart * 360.0 - 90.0; + var secondHandLength = radius - 35; // Longest hand + + // Draw second hand first (if in high power mode), so it appears behind other hands + if (isHighPowerMode) { + drawSingleHand(dc, centerX, centerY, secondAngle, secondHandLength, 2, Graphics.COLOR_DK_GRAY); + } + // Draw minute hand (longer, thinner) drawSingleHand(dc, centerX, centerY, minuteAngle, minuteHandLength, 4, Graphics.COLOR_WHITE);