initial commit

This commit is contained in:
2025-10-09 14:37:45 +03:00
commit b440432ca2
17 changed files with 477 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
bin/

37
manifest.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0"?>
<!-- This is a generated file. It is highly recommended that you DO NOT edit this file. -->
<iq:manifest version="3" xmlns:iq="http://www.garmin.com/xml/connectiq">
<!--
Use "Monkey C: Edit Application" from the Visual Studio Code command palette
to update the application attributes.
-->
<iq:application id="87cf1f68-dcef-4751-bf6f-9337d59b08dd" type="watchface" name="@Strings.AppName" entry="wfApp" launcherIcon="@Drawables.LauncherIcon" minApiLevel="5.2.0">
<!--
Use the following from the Visual Studio Code comand palette to edit
the build targets:
"Monkey C: Set Products by Product Category" - Lets you add all products
that belong to the same product category
"Monkey C: Edit Products" - Lets you add or remove any product
-->
<iq:products>
<iq:product id="fenix8pro47mm"/>
</iq:products>
<!--
Use "Monkey C: Edit Permissions" from the Visual Studio Code command
palette to update permissions.
-->
<iq:permissions/>
<!--
Use "Monkey C: Edit Languages" from the Visual Studio Code command
palette to edit your compatible language list.
-->
<iq:languages>
<iq:language>eng</iq:language>
</iq:languages>
<!--
Use "Monkey C: Configure Monkey Barrel" from the Visual Studio Code
command palette to edit the included barrels.
-->
<iq:barrels/>
</iq:application>
</iq:manifest>

1
monkey.jungle Executable file
View File

@ -0,0 +1 @@
project.manifest = manifest.xml

View File

@ -0,0 +1,3 @@
<drawables xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://developer.garmin.com/downloads/connect-iq/resources.xsd">
<bitmap id="LauncherIcon" filename="launcher_icon.svg" dithering="none" />
</drawables>

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="#000000"/>
<path d="M12.1941 10.8471L16.1353 7.30588L16.9235 8.17646L13.1059 11.6059C13.1471 11.7294 13.1765 11.8588 13.1765 12C13.1765 12.6471 12.6471 13.1765 12 13.1765C11.701 13.1765 11.4272 13.0634 11.219 12.878L5.90332 15.9461L5.60926 15.4367L10.8899 12.3888C10.8469 12.2669 10.8235 12.1361 10.8235 12C10.8235 11.9824 10.8265 11.9662 10.8294 11.95C10.8324 11.9338 10.8353 11.9176 10.8353 11.9L8.12353 10.0471L8.78235 9.07058L11.5059 10.9353C11.6588 10.8647 11.8235 10.8235 12 10.8235C12.0647 10.8235 12.1294 10.8353 12.1941 10.8471Z" fill="#F4F4F4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM11.4118 20.2146V18.4706H12.5882V20.2146C16.6676 19.9267 19.9267 16.6676 20.2146 12.5882H18.4706V11.4118H20.2146C19.9267 7.33237 16.6676 4.07329 12.5882 3.78539V5.52941H11.4118V3.78539C7.33237 4.07329 4.07329 7.33237 3.78539 11.4118H5.52941V12.5882H3.78539C4.07329 16.6676 7.33237 19.9267 11.4118 20.2146Z" fill="#F4F4F4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,7 @@
<properties xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://developer.garmin.com/downloads/connect-iq/resources.xsd">
<property id="BackgroundColor" type="number">0x000000</property>
<property id="ForegroundColor" type="number">0xFF0000</property>
<property id="UseMilitaryFormat" type="boolean">false</property>
</properties>

25
resources/settings/settings.xml Executable file
View File

@ -0,0 +1,25 @@
<settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://developer.garmin.com/downloads/connect-iq/resources.xsd">
<setting propertyKey="@Properties.BackgroundColor" title="@Strings.BackgroundColorTitle">
<settingConfig type="list">
<listEntry value="0x000000">@Strings.ColorBlack</listEntry>
<listEntry value="0x555555">@Strings.ColorDarkGray</listEntry>
<listEntry value="0xAAAAAA">@Strings.ColorLightGray</listEntry>
<listEntry value="0xFFFFFF">@Strings.ColorWhite</listEntry>
</settingConfig>
</setting>
<setting propertyKey="@Properties.ForegroundColor" title="@Strings.ForegroundColorTitle">
<settingConfig type="list">
<listEntry value="0x000000">@Strings.ColorBlack</listEntry>
<listEntry value="0x0000FF">@Strings.ColorBlue</listEntry>
<listEntry value="0xFF0000">@Strings.ColorRed</listEntry>
<listEntry value="0xFFFFFF">@Strings.ColorWhite</listEntry>
</settingConfig>
</setting>
<setting propertyKey="@Properties.UseMilitaryFormat" title="@Strings.MilitaryFormatTitle">
<settingConfig type="boolean" />
</setting>
</settings>

View File

@ -0,0 +1,16 @@
<strings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://developer.garmin.com/downloads/connect-iq/resources.xsd">
<string id="AppName">wf</string>
<string id="BackgroundColorTitle">Background Color</string>
<string id="ForegroundColorTitle">Foreground Color</string>
<string id="MilitaryFormatTitle">Military Format for 24 Hour Time</string>
<string id="ColorBlack">Black</string>
<string id="ColorDarkGray">Dark Gray</string>
<string id="ColorLightGray">Light Gray</string>
<string id="ColorWhite">White</string>
<string id="ColorBlue">Blue</string>
<string id="ColorRed">Red</string>
</strings>

View File

@ -0,0 +1,44 @@
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
class IBackground extends Drawable {
var Marks as Array<IMark> = [];
typedef BackgroundParams as {
:Identifier as Object,
};
enum BackgroundStyleType {
SOLID_BACKGROUND,
}
static function getBackground(style as BackgroundStyleType) as IBackground {
switch (style) {
case SOLID_BACKGROUND:
default:
return new SolidBackground({});
}
}
function initialize(options as BackgroundParams) {
Drawable.initialize({:identifier => options[:Identifier]});
for (var s = 0; s < 60; s += 5) {
var mark = IMark.getMark(IMark.LINE_MARK, {:Seconds => s});
if (mark != null) {
Marks.add(mark);
}
}
}
function draw(dc as Dc) as Void {
drawBackground(dc);
for (var i = 0; i < Marks.size(); ++i) {
Marks[i].draw(dc);
}
}
function drawBackground(dc as Dc) as Void {}
}

View File

@ -0,0 +1,82 @@
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
class IMark extends Drawable {
var CenterShift as [Float, Float];
var Color as ColorType;
var Radius as Float;
var Seconds as Number;
typedef MarkParams as {
:Identifier as Object,
:Type as MarkType,
:Seconds as Number,
:Color as ColorType,
:HandsParams as IHands.HandsParams,
};
enum MarkType {
START_MARK,
PRIMARY_MARK,
SECONDARY_MARK,
TERTIARY_MARK,
}
enum MarkStyleType {
LINE_MARK,
DOT_MARK,
ARABIC_MARK,
EMPTY_MARK,
}
static function getMark(style as MarkStyleType, options as MarkParams) as IMark? {
switch (style) {
case EMPTY_MARK:
return null;
case LINE_MARK:
default:
return new LineMark(options);
}
}
function initialize(options as MarkParams) {
Drawable.initialize({:identifier => options[:Identifier]});
CenterShift = getOrElse(getOrElse(options[:HandsParams], {})[:CenterShift], [0.0, 0.0]);
Color = getOrElse(options[:Color], Graphics.COLOR_WHITE);
Radius = getOrElse(options[:Radius], 1.0);
Seconds = options[:Seconds];
}
function draw(dc as Dc) as Void {
var center = getCenter(dc, CenterShift);
var length = min(center[0], center[1]);
drawMark(dc, center[0], center[1], length);
}
function drawMark(dc as Dc, x as Float, y as Float, length as Float) as Void {}
function getMarkType() as MarkType {
switch (Seconds) {
case 0:
return START_MARK;
case 15:
case 30:
case 45:
return PRIMARY_MARK;
case 5:
case 10:
case 20:
case 25:
case 35:
case 40:
case 50:
case 55:
return SECONDARY_MARK;
default:
return TERTIARY_MARK;
}
}
}

View File

@ -0,0 +1,20 @@
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
class LineMark extends IMark {
var InnerRadius as Float = 0.9;
function initialize(options as IMark.MarkParams) {
IMark.initialize(options);
}
function drawMark(dc as Dc, x as Float, y as Float, length as Float) as Void {
var angle = Math.toRadians(Seconds * 6.0 - 90);
dc.setColor(Color, Color);
dc.drawLine(x + length * InnerRadius * Math.cos(angle), y + length * InnerRadius * Math.sin(angle),
x + length * Radius * Math.cos(angle), y + length * Radius * Math.sin(angle));
}
}

View File

@ -0,0 +1,24 @@
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.WatchUi;
class SolidBackground extends IBackground {
var Color as ColorType;
typedef SolidBackgroundParams as {
:BackgroundParams as IBackground.BackgroundParams,
:Color as ColorType,
};
function initialize(options as SolidBackgroundParams) {
IBackground.initialize(getOrElse(options[:BackgroundParams], {}));
Color = getOrElse(options[:Color], Graphics.COLOR_BLACK);
}
function drawBackground(dc as Dc) as Void {
dc.setColor(Graphics.COLOR_TRANSPARENT, Color);
dc.clear();
}
}

59
source/Hands/IHands.mc Normal file
View File

@ -0,0 +1,59 @@
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.System;
import Toybox.WatchUi;
class IHands extends Drawable {
var CenterShift as [Float, Float];
var Radius as Float;
typedef HandsParams as {
:CenterShift as [Float, Float],
:Identifier as Object,
:Radius as Float,
};
enum HandType {
HOURS_HAND,
MINUTES_HAND,
SECONDS_HAND,
}
enum HandStyleType {
SIMPLE_HANDS,
}
static function getHands(style as HandStyleType) as IHands {
switch (style) {
case SIMPLE_HANDS:
default:
return new SimpleHands({});
}
}
function initialize(options as HandsParams) {
Drawable.initialize({:identifier => options[:Identifier]});
CenterShift = getOrElse(options[:CenterShift], [0.0, 0.0]);
Radius = getOrElse(options[:Radius], 0.9);
}
function draw(dc as Dc) as Void {
var now = System.getClockTime();
var center = getCenter(dc, CenterShift);
var length = Radius * min(center[0], center[1]);
var hourAngle = (now.hour % 12 + now.min / 60.0) * 30.0;
drawHand(dc, center[0], center[1], hourAngle, length, HOURS_HAND);
var minutesAngle = now.min * 6.0;
drawHand(dc, center[0], center[1], minutesAngle, length, MINUTES_HAND);
var secondsAngle = now.sec * 6.0;
drawHand(dc, center[0], center[1], secondsAngle, length, SECONDS_HAND);
}
function drawHand(dc as Dc, x as Float, y as Float, angle as Float, length as Float, handType as HandType) as Void {}
}

View File

@ -0,0 +1,48 @@
import Toybox.Graphics;
import Toybox.Lang;
class SimpleHands extends IHands {
var Color as ColorType;
var SecondsColor as ColorType;
typedef SimpleHandsParams as {
:HandsParams as IHands.HandsParams,
:Color as ColorType,
:SecondsColor as ColorType,
};
function initialize(options as SimpleHandsParams) {
IHands.initialize(getOrElse(options[:HandsParams], {}));
Color = getOrElse(options[:Color], Graphics.COLOR_WHITE);
SecondsColor = getOrElse(options[:SecondsColor], Graphics.COLOR_RED);
}
function drawHand(dc as Dc, x as Float, y as Float, angle as Float, length as Float, handType as IHands.HandType) as Void {
var color = getColor(handType);
angle = Math.toRadians(angle - 90);
length *= getLenght(handType);
dc.setColor(color, color);
dc.drawLine(x, y, x + length * Math.cos(angle), y + length * Math.sin(angle));
}
private function getColor(handType as IHands.HandType) as ColorType {
switch (handType) {
case SECONDS_HAND:
return SecondsColor;
default:
return Color;
}
}
private function getLenght(handType as IHands.HandType) as Float {
switch (handType) {
case HOURS_HAND:
return 0.7;
default:
return 1.0;
}
}
}

23
source/Utils.mc Normal file
View File

@ -0,0 +1,23 @@
import Toybox.Lang;
import Toybox.Graphics;
// no types here, because this is generic, which are not supported by language
function getOrElse(value, defaultValue) {
if (value == null) {
return defaultValue;
} else {
return value;
}
}
function min(left as Numeric, right as Numeric) as Numeric {
if (left < right) {
return left;
} else {
return right;
}
}
function getCenter(dc as Dc, shift as [Float, Float]) as [Float, Float] {
return [dc.getWidth() / 2.0 + shift[0], dc.getHeight() / 2.0 + shift[1]];
}

33
source/wfApp.mc Normal file
View File

@ -0,0 +1,33 @@
import Toybox.Application;
import Toybox.Lang;
import Toybox.WatchUi;
class wfApp extends Application.AppBase {
function initialize() {
AppBase.initialize();
}
// onStart() is called on application start up
function onStart(state as Dictionary?) as Void {
}
// onStop() is called when your application is exiting
function onStop(state as Dictionary?) as Void {
}
// Return the initial view of your application here
function getInitialView() as [Views] or [Views, InputDelegates] {
return [ new wfView() ];
}
// New app settings have been received so trigger a UI update
function onSettingsChanged() as Void {
WatchUi.requestUpdate();
}
}
function getApp() as wfApp {
return Application.getApp() as wfApp;
}

49
source/wfView.mc Normal file
View File

@ -0,0 +1,49 @@
import Toybox.Application;
import Toybox.Graphics;
import Toybox.Lang;
import Toybox.System;
import Toybox.WatchUi;
class wfView extends WatchUi.WatchFace {
private var hands as IHands;
private var background as IBackground;
function initialize() {
WatchFace.initialize();
hands = IHands.getHands(IHands.SIMPLE_HANDS);
background = IBackground.getBackground(IBackground.SOLID_BACKGROUND);
}
// Load your resources here
function onLayout(dc as Dc) as Void {
}
// 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() as Void {
}
// Update the view
function onUpdate(dc as Dc) as Void {
background.draw(dc);
hands.draw(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() as Void {
}
// The user has just looked at their watch. Timers and animations may be started here.
function onExitSleep() as Void {
}
// Terminate any active timers and prepare for slow updates.
function onEnterSleep() as Void {
}
}