From fbac0449b785ef865ee4727aa6cdc2b39315dddb Mon Sep 17 00:00:00 2001 From: cy384 Date: Tue, 21 May 2019 20:15:35 -0400 Subject: Inherited code from github.com/cy384/copernicus Original repository used as base to quickly get a working watchface app. --- .project | 17 ++ LICENSE | 27 +++ README.md | 27 +++ manifest.xml | 41 +++++ monkey.jungle | 1 + resources/drawables.xml | 3 + resources/launcher_icon.png | Bin 0 -> 1292 bytes resources/properties.xml | 7 + resources/settings.xml | 11 ++ resources/strings.xml | 8 + source/CopernicusApp.mc | 38 +++++ source/CopernicusView.mc | 397 ++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 577 insertions(+) create mode 100644 .project create mode 100644 LICENSE create mode 100644 README.md create mode 100644 manifest.xml create mode 100644 monkey.jungle create mode 100644 resources/drawables.xml create mode 100644 resources/launcher_icon.png create mode 100644 resources/properties.xml create mode 100644 resources/settings.xml create mode 100644 resources/strings.xml create mode 100644 source/CopernicusApp.mc create mode 100644 source/CopernicusView.mc diff --git a/.project b/.project new file mode 100644 index 0000000..a4cc0fd --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + Copernicus + + + + + + connectiq.builder + + + + + + connectiq.projectNature + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f33ac8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (C) 2019 cy384 +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the names of the copyright holders nor the names of any + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..5782655 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +Copernicus +========== +![Copernicus](https://i.imgur.com/UHR9TMi.png) + +A digital homage to the Raketa Kopernik mechanical watch, inspired by the orbits of the sun and the moon, with a few configurable features: + +* Dark and light variants + +* Enable/disable arbor + +* Enable/disable seconds hand + +The design is high-contrast and intended to be easily readable with or without the backlight active. + +It may be found [on the Garmin store](https://apps.garmin.com/en-US/apps/7f9c1277-ffb8-44f6-ab86-963ad88c85a6). + +Building +-------- +Follow the Garmin instructions for installing and configuring the SDK, along with Eclipse. Note the key requirements, etc. Unfortunately, you cannot, as far as I can tell, build the bundle necessary to submit to the Garmin store without using their Eclipse plugins (also the SDK tools don't seem to run on Ubuntu without fiddling anymore?). This version builds with the ConnectIQ SDK version `3.0.11`. + +After you do a build, you can deploy it locally by just copying the `.prg` file over USB to your watch. + +Open Source +----------- +I'm releasing this as open source in the hopes that someone may find it interesting or useful. The only mildly clever feature in the code is that things drawn on-screen are scaled to the screen size in-code for each device. This lets the watchface port trivially to all Garmin watches, regardless of shape and pixel count. + +This code is covered by the included BSD 3-clause license (see LICENSE file). Any patches, bug reports, or other user commments are greatly appreciated. I will note that I personally consider this feature-complete, and am not likely to merge features that make the design more complicated. Please feel free to fork if you plan on signficant additions. diff --git a/manifest.xml b/manifest.xml new file mode 100644 index 0000000..847cefd --- /dev/null +++ b/manifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eng + + + + diff --git a/monkey.jungle b/monkey.jungle new file mode 100644 index 0000000..b2200b1 --- /dev/null +++ b/monkey.jungle @@ -0,0 +1 @@ +project.manifest = manifest.xml diff --git a/resources/drawables.xml b/resources/drawables.xml new file mode 100644 index 0000000..4a90222 --- /dev/null +++ b/resources/drawables.xml @@ -0,0 +1,3 @@ + + + diff --git a/resources/launcher_icon.png b/resources/launcher_icon.png new file mode 100644 index 0000000..d3594e7 Binary files /dev/null and b/resources/launcher_icon.png differ diff --git a/resources/properties.xml b/resources/properties.xml new file mode 100644 index 0000000..fd9cb4a --- /dev/null +++ b/resources/properties.xml @@ -0,0 +1,7 @@ + + + true + true + true + + diff --git a/resources/settings.xml b/resources/settings.xml new file mode 100644 index 0000000..e17851e --- /dev/null +++ b/resources/settings.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/resources/strings.xml b/resources/strings.xml new file mode 100644 index 0000000..e16a8a9 --- /dev/null +++ b/resources/strings.xml @@ -0,0 +1,8 @@ + + Copernicus + + + Dark mode? + Display second hand? + Display arbor? + diff --git a/source/CopernicusApp.mc b/source/CopernicusApp.mc new file mode 100644 index 0000000..982f397 --- /dev/null +++ b/source/CopernicusApp.mc @@ -0,0 +1,38 @@ +// +// Copernicus +// +// Copyright (C) 2019 cy384 +// All rights reserved. +// +// This software may be modified and distributed under the terms +// of the BSD license. See the LICENSE file for details. +// + +using Toybox.Application; +using Toybox.WatchUi; + +class CopernicusApp extends Application.AppBase +{ + function initialize() + { + AppBase.initialize(); + } + + function onStart(state) + { + } + + function onStop(state) + { + } + + function getInitialView() + { + return [new CopernicusView()]; + } + + function onSettingsChanged() + { + WatchUi.requestUpdate(); + } +} diff --git a/source/CopernicusView.mc b/source/CopernicusView.mc new file mode 100644 index 0000000..bbb8b5e --- /dev/null +++ b/source/CopernicusView.mc @@ -0,0 +1,397 @@ +// +// Copernicus +// +// Copyright (C) 2019 cy384 +// All rights reserved. +// +// This software may be modified and distributed under the terms +// of the BSD license. See the LICENSE file for details. +// + +using Toybox.WatchUi; +using Toybox.Graphics; +using Toybox.System; +using Toybox.Math; +using Toybox.Application; + +class CopernicusView extends WatchUi.WatchFace +{ + var awake; + var arbor; + var seconds; + var dark; + + function initialize() + { + WatchUi.WatchFace.initialize(); + + awake = false; + arbor = true; + seconds = true; + dark = true; + } + + function onLayout(dc) + { + } + + // "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() { + } + + function drawMinutesMarks(dc) + { + var width = dc.getWidth(); + var height = dc.getHeight(); + + var min; + if (width > height) + { + min = height; + } + else + { + min = width; + } + + var iX, iY, oX, oY; + var furtherOuterRad = min * 0.37; + var outerRad = min * 0.35; + var innerRad = min * 0.33; + + // circles + dc.setPenWidth(1); + dc.drawCircle(width/2.0, height/2.0, outerRad); + dc.drawCircle(width/2.0, height/2.0, innerRad); + + if (dark) + { + dc.setColor(Graphics.COLOR_LT_GRAY, Graphics.COLOR_TRANSPARENT); + } + else + { + dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT); + } + + // minute marks + dc.setPenWidth(1); + for (var x = 0; x <= 60; x += 1) { + var angle = x * (Math.PI / 30.0); + + // make the 5 minute marks longer + if (x % 5 == 0) + { + oX = width/2.0 + Math.sin(angle)*furtherOuterRad; + oY = height/2.0 + Math.cos(angle)*furtherOuterRad; + } + else + { + oX = width/2.0 + Math.sin(angle)*outerRad; + oY = height/2.0 + Math.cos(angle)*outerRad; + } + + iX = width/2.0 + Math.sin(angle)*innerRad; + iY = height/2.0 + Math.cos(angle)*innerRad; + + dc.drawLine(iX, iY, oX, oY); + } + } + + function drawMinuteHand(dc) + { + var width = dc.getWidth(); + var height = dc.getHeight(); + + var min; + if (width > height) + { + min = height; + } + else + { + min = width; + } + + var clockTime = System.getClockTime(); + + var hourRad = min * 0.18; + var pointerOut = min * 0.42; + var pointerIn = min * 0.36; + + // get the angle for minutes + var angle = (clockTime.min / 60.0) * (-2.0 * Math.PI); + angle += Math.PI; + + // center of the hour circle + var cX = width/2.0 + Math.sin(angle)*hourRad; + var cY = height/2.0 + Math.cos(angle)*hourRad; + + // points for the hour nub + var pox = width/2.0 + Math.sin(angle)*pointerOut; + var poy = height/2.0 + Math.cos(angle)*pointerOut; + + var pix = width/2.0 + Math.sin(angle)*pointerIn; + var piy = height/2.0 + Math.cos(angle)*pointerIn; + + // draw the hand + dc.setPenWidth(4); + dc.drawLine(pix, piy, pox, poy); + dc.drawCircle(cX, cY, min/4.8); + + if (arbor) + { + // draw the arbor + dc.fillCircle(width/2.0, height/2.0, min*0.03); + } + } + + function drawHourHand(dc) + { + var width = dc.getWidth(); + var height = dc.getHeight(); + + var min; + if (width > height) + { + min = height; + } + else + { + min = width; + } + + var clockTime = System.getClockTime(); + + var hourRad = min * 0.13; + var pointerOut = min * 0.34; + var pointerIn = min * 0.30; + + // get the angle for the hour + var angle = ((clockTime.hour % 12) / 12.0) * (-2.0 * Math.PI); + angle += Math.PI; + + // add the minutes + angle -= (clockTime.min / 60.0) * (Math.PI / 6); + + // center of the circle + var cX = width/2.0 + Math.sin(angle)*hourRad; + var cY = height/2.0 + Math.cos(angle)*hourRad; + + // points for the nub + var pox = width/2.0 + Math.sin(angle)*pointerOut; + var poy = height/2.0 + Math.cos(angle)*pointerOut; + + var pix = width/2.0 + Math.sin(angle)*pointerIn; + var piy = height/2.0 + Math.cos(angle)*pointerIn; + + dc.setPenWidth(5); + dc.drawLine(pix, piy, pox, poy); + dc.fillCircle(cX, cY, min/5.3); + } + + function drawSecondHand(dc) + { + var width = dc.getWidth(); + var height = dc.getHeight(); + + var min; + if (width > height) + { + min = height; + } + else + { + min = width; + } + + var clockTime = System.getClockTime(); + + var secondRad = min * 0.45; + var secondRadBack = min * 0.2; + + // get the angle for seconds + var angle = (clockTime.sec / 60.0) * (-2.0 * Math.PI); + angle += Math.PI; + + // tip of the second hand + var slx = width/2.0 + Math.sin(angle)*secondRad; + var sly = height/2.0 + Math.cos(angle)*secondRad; + + // draw second hand + dc.setPenWidth(2); + dc.drawLine(width/2.0, height/2.0, slx, sly); + + // back side of second hand + var back = angle + Math.PI; + var bx = width/2.0 + Math.sin(back)*secondRadBack; + var by = height/2.0 + Math.cos(back)*secondRadBack; + + dc.setPenWidth(2); + dc.drawLine(width/2.0, height/2.0, bx, by); + + // make a triangle shape on the back + var left = back - 0.02*Math.PI; + var right = back + 0.02*Math.PI; + + dc.fillPolygon([[width/2.0, height/2.0], + [width/2.0 + Math.sin(left)*secondRadBack, + height/2.0 + Math.cos(left)*secondRadBack], + [width/2.0 + Math.sin(right)*secondRadBack, + height/2.0 + Math.cos(right)*secondRadBack]]); + + if (arbor) + { + // draw the arbor + dc.fillCircle(width/2.0, height/2.0, min*0.02); + } + } + + function drawDots(dc) + { + var width = dc.getWidth(); + var height = dc.getHeight(); + + var min; + if (width > height) + { + min = height; + } + else + { + min = width; + } + + // top dots + if (dark) + { + // dark variant has two dots on top + dc.fillCircle(width * 0.46, height * 0.04, min/33); + dc.fillCircle(width * 0.54, height * 0.04, min/33); + } + else + { + dc.fillCircle(width * 0.50, height * 0.04, min/33); + } + + // other cardinal dots + dc.fillCircle(width * 0.50, height * 0.96, min/33); + dc.fillCircle(width * 0.96, height * 0.50, min/33); + dc.fillCircle(width * 0.04, height * 0.50, min/33); + + if (!dark) + { + // light variant has more dots at 45* angles + // aka sqrt(3)/2 + var ord = 0.8660254037844386; + + // hour dots + dc.fillCircle(width*0.5*(1 + 0.92*ord), height*0.5*(1 + 0.92*0.5), min/40); + dc.fillCircle(width*0.5*(1 + 0.92*ord), height*0.5*(1 - 0.92*0.5), min/40); + dc.fillCircle(width*0.5*(1 - 0.92*ord), height*0.5*(1 + 0.92*0.5), min/40); + dc.fillCircle(width*0.5*(1 - 0.92*ord), height*0.5*(1 - 0.92*0.5), min/40); + + dc.fillCircle(width*0.5*(1 + 0.92*0.5), height*0.5*(1 + 0.92*ord), min/40); + dc.fillCircle(width*0.5*(1 + 0.92*0.5), height*0.5*(1 - 0.92*ord), min/40); + dc.fillCircle(width*0.5*(1 - 0.92*0.5), height*0.5*(1 + 0.92*ord), min/40); + dc.fillCircle(width*0.5*(1 - 0.92*0.5), height*0.5*(1 - 0.92*ord), min/40); + } + } + + // update event handler + function onUpdate(dc) + { + dark = Application.getApp().getProperty("display_dark"); + arbor = Application.getApp().getProperty("display_arbor"); + seconds = Application.getApp().getProperty("display_seconds"); + + var width = dc.getWidth(); + var height = dc.getHeight(); + + var min; + if (width > height) + { + min = height; + } + else + { + min = width; + } + + // bg + if (dark) + { + dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT); + } + else + { + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + } + dc.fillRectangle(0, 0, width, height); + + // draw all dots + dc.setColor(Graphics.COLOR_YELLOW, Graphics.COLOR_TRANSPARENT); + drawDots(dc); + + // minute rings with marks + if (dark) + { + dc.setColor(Graphics.COLOR_LT_GRAY, Graphics.COLOR_TRANSPARENT); + } + else + { + dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT); + } + drawMinutesMarks(dc); + + // draw hour hand + dc.setColor(Graphics.COLOR_YELLOW, Graphics.COLOR_TRANSPARENT); + drawHourHand(dc); + + // draw minute hand + if (dark) + { + dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT); + } + else + { + dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT); + } + drawMinuteHand(dc); + + // if we're awake, draw the second hand + if (awake && seconds) + { + if (dark) + { + dc.setColor(Graphics.COLOR_LT_GRAY, Graphics.COLOR_TRANSPARENT); + } + else + { + dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT); + } + + drawSecondHand(dc); + } + } + + // "Called when this View is removed from the screen. Save the + // state of this View here. This includes freeing resources from + // memory." + function onHide() { + } + + // called when we enter sleep + function onEnterSleep() + { + awake = false; + WatchUi.requestUpdate(); + } + + // called when we exit sleep + function onExitSleep() + { + awake = true; + } +} -- cgit v1.2.3