commit 3cc09ef5b747c3a926af53615d928843b9a38a67 Author: Evgenii Alekseev Date: Thu Oct 9 14:37:45 2025 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e660fd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/manifest.xml b/manifest.xml new file mode 100644 index 0000000..b3fa4e0 --- /dev/null +++ b/manifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + eng + + + + + \ No newline at end of file diff --git a/monkey.jungle b/monkey.jungle new file mode 100755 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 100755 index 0000000..6302154 --- /dev/null +++ b/resources/drawables/drawables.xml @@ -0,0 +1,3 @@ + + + diff --git a/resources/drawables/launcher_icon.svg b/resources/drawables/launcher_icon.svg new file mode 100755 index 0000000..e80aa20 --- /dev/null +++ b/resources/drawables/launcher_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/settings/properties.xml b/resources/settings/properties.xml new file mode 100755 index 0000000..0863388 --- /dev/null +++ b/resources/settings/properties.xml @@ -0,0 +1,24 @@ + + + 0x000000 + 0xFF0000 + false + + + + 2 + 0xFFFFFF + + + 1 + 0xFFFFFF + + + 1 + 0xFFFFFF + + + 1 + 0xFFFFFF + + diff --git a/resources/settings/settings.xml b/resources/settings/settings.xml new file mode 100755 index 0000000..47f6c80 --- /dev/null +++ b/resources/settings/settings.xml @@ -0,0 +1,25 @@ + + + + + @Strings.ColorBlack + @Strings.ColorDarkGray + @Strings.ColorLightGray + @Strings.ColorWhite + + + + + + @Strings.ColorBlack + @Strings.ColorBlue + @Strings.ColorRed + @Strings.ColorWhite + + + + + + + + diff --git a/resources/strings/strings.xml b/resources/strings/strings.xml new file mode 100644 index 0000000..1754403 --- /dev/null +++ b/resources/strings/strings.xml @@ -0,0 +1,16 @@ + + + wf + + Background Color + Foreground Color + Military Format for 24 Hour Time + + Black + Dark Gray + Light Gray + White + Blue + Red + + diff --git a/source/Background/IBackground.mc b/source/Background/IBackground.mc new file mode 100644 index 0000000..4c67ce7 --- /dev/null +++ b/source/Background/IBackground.mc @@ -0,0 +1,56 @@ +import Toybox.Application.Properties; +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class IBackground extends Drawable { + + var Marks as Array = []; + + typedef BackgroundParams as { + :Identifier as Object, + :Color as ColorType, + }; + + enum BackgroundStyleType { + SOLID_BACKGROUND, + } + + static function getBackground(style as BackgroundStyleType, options as BackgroundParams) as IBackground { + switch (style) { + case SOLID_BACKGROUND: + default: + return new SolidBackground(options); + } + } + + function initialize(options as BackgroundParams) { + var identifier = options[:Identifier]; + Drawable.initialize({:identifier => identifier}); + + for (var s = 0; s < 60; s += 1) { + var markType = IMark.getMarkType(s); + + var markIdentifier = Lang.format("$1$/Marks/$2$", [identifier, markType]); + var markStyle = Properties.getValue(Lang.format("$1$/Type", [markIdentifier])) as IMark.MarkStyleType; + + var mark = IMark.getMark(markStyle, { + :Identifier => markIdentifier, + :Seconds => s, + :Color => Properties.getValue(Lang.format("$1$/Color", [markIdentifier])) as ColorType, + }); + 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 {} +} diff --git a/source/Background/Marks/ArabicMark.mc b/source/Background/Marks/ArabicMark.mc new file mode 100644 index 0000000..58926d2 --- /dev/null +++ b/source/Background/Marks/ArabicMark.mc @@ -0,0 +1,29 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class ArabicMark extends IMark { + + var Font as FontType; + + function initialize(options as IMark.MarkParams) { + IMark.initialize(options); + + Font = getOrElse(options[:Font], Graphics.FONT_SMALL); + } + + function drawMark(dc as Dc, x as Float, y as Float, length as Float) as Void { + var angle = getAngle(); + var text = secondsToText(); + var dimentions = dc.getTextDimensions(text, Font); + + dc.setColor(Color, Graphics.COLOR_TRANSPARENT); + dc.drawText(x + length * InnerRadius * Math.cos(angle) - dimentions[0] / 2.0, + y + length * InnerRadius * Math.sin(angle) - dimentions[1] / 2.0, + Font, text, Graphics.TEXT_JUSTIFY_LEFT); + } + + function secondsToText() as String? { + return getHours().toString(); + } +} \ No newline at end of file diff --git a/source/Background/Marks/DotMark.mc b/source/Background/Marks/DotMark.mc new file mode 100644 index 0000000..e82ecd5 --- /dev/null +++ b/source/Background/Marks/DotMark.mc @@ -0,0 +1,18 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class DotMark extends IMark { + + 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 = getAngle(); + + dc.setColor(Color, Graphics.COLOR_TRANSPARENT); + dc.fillCircle(x + length * InnerRadius * Math.cos(angle), y + length * InnerRadius * Math.sin(angle), + length * Size / 2.0); + } +} \ No newline at end of file diff --git a/source/Background/Marks/DoubleLineMark.mc b/source/Background/Marks/DoubleLineMark.mc new file mode 100644 index 0000000..e979f64 --- /dev/null +++ b/source/Background/Marks/DoubleLineMark.mc @@ -0,0 +1,22 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class DoubleLineMark extends IMark { + + var Offset as Float = Math.toRadians(1.0); + + 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 = getAngle(); + + dc.setColor(Color, Color); + dc.drawLine(x + length * InnerRadius * Math.cos(angle - Offset), y + length * InnerRadius * Math.sin(angle - Offset), + x + length * Radius * Math.cos(angle - Offset), y + length * Radius * Math.sin(angle - Offset)); + dc.drawLine(x + length * InnerRadius * Math.cos(angle + Offset), y + length * InnerRadius * Math.sin(angle + Offset), + x + length * Radius * Math.cos(angle + Offset), y + length * Radius * Math.sin(angle + Offset)); + } +} \ No newline at end of file diff --git a/source/Background/Marks/IMark.mc b/source/Background/Marks/IMark.mc new file mode 100644 index 0000000..daa0d03 --- /dev/null +++ b/source/Background/Marks/IMark.mc @@ -0,0 +1,121 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class IMark extends Drawable { + + var CenterShift as [Float, Float]; + var Color as ColorType; + var InnerRadius as Float; + var Radius as Float; + var Seconds as Number; + var Size as Float; + + typedef MarkParams as { + :Identifier as Object, + :Seconds as Number, + :Color as ColorType, + :Font as FontType, // not used + :Size as Float, + :HandsParams as IHands.HandsParams, + }; + + enum MarkType { + START_MARK = 0, + PRIMARY_MARK = 1, + SECONDARY_MARK = 2, + TERTIARY_MARK = 3, + } + + enum MarkStyleType { + EMPTY_MARK = 0, + LINE_MARK = 1, + DOUBLE_LINE_MARK = 2, + DOT_MARK = 3, + ARABIC_MARK = 4, + ROMAN_MARK = 5, + } + + static function getMark(style as MarkStyleType, options as MarkParams) as IMark? { + switch (style) { + case LINE_MARK: + return new LineMark(options); + case DOUBLE_LINE_MARK: + return new DoubleLineMark(options); + case DOT_MARK: + return new DotMark(options); + case ARABIC_MARK: + return new ArabicMark(options); + case ROMAN_MARK: + return new RomanMark(options); + case EMPTY_MARK: + default: + return null; + } + } + + static function getMarkType(seconds as Number) 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; + } + } + + function initialize(options as MarkParams) { + Drawable.initialize({:identifier => options[:Identifier]}); + + var handsParams = getOrElse(options[:HandsParams], {}) as IHands.HandsParams; + + // scene + CenterShift = getOrElse(handsParams[:CenterShift], [0.0, 0.0]); + Radius = getOrElse(handsParams[:Radius], 1.0); + + // properties + Color = getOrElse(options[:Color], Graphics.COLOR_WHITE); + Seconds = options[:Seconds]; + Size = getOrElse(options[:Size], 0.1); + + // calculated + InnerRadius = Radius * (1 - Size); + } + + 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 getAngle() as Float { + return Math.toRadians(Seconds * 6.0 - 90); + } + + function getHours() as Number? { + if (Seconds % 5 != 0) { + return null; + } + + var hours = Seconds / 5; + if (hours == 0) { + return 12; + } + + return hours; + } +} \ No newline at end of file diff --git a/source/Background/Marks/LineMark.mc b/source/Background/Marks/LineMark.mc new file mode 100644 index 0000000..b4301a9 --- /dev/null +++ b/source/Background/Marks/LineMark.mc @@ -0,0 +1,18 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class LineMark extends IMark { + + 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 = getAngle(); + + 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)); + } +} \ No newline at end of file diff --git a/source/Background/Marks/RomanMark.mc b/source/Background/Marks/RomanMark.mc new file mode 100644 index 0000000..262f9be --- /dev/null +++ b/source/Background/Marks/RomanMark.mc @@ -0,0 +1,41 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class RomanMark extends ArabicMark { + + function initialize(options as IMark.MarkParams) { + ArabicMark.initialize(options); + } + + function secondsToText() as String? { + switch (getHours()) { + case 1: + return "I"; + case 2: + return "II"; + case 3: + return "III"; + case 4: + return "IV"; + case 5: + return "V"; + case 6: + return "VI"; + case 7: + return "VII"; + case 8: + return "VIII"; + case 9: + return "IX"; + case 10: + return "X"; + case 11: + return "XI"; + case 12: + return "XII"; + default: + return null; + } + } +} \ No newline at end of file diff --git a/source/Background/SolidBackground.mc b/source/Background/SolidBackground.mc new file mode 100644 index 0000000..8743d25 --- /dev/null +++ b/source/Background/SolidBackground.mc @@ -0,0 +1,19 @@ +import Toybox.Graphics; +import Toybox.Lang; +import Toybox.WatchUi; + +class SolidBackground extends IBackground { + + var Color as ColorType; + + function initialize(options as IBackground.BackgroundParams) { + IBackground.initialize(options); + + Color = getOrElse(options[:Color], Graphics.COLOR_BLACK); + } + + function drawBackground(dc as Dc) as Void { + dc.setColor(Graphics.COLOR_TRANSPARENT, Color); + dc.clear(); + } +} \ No newline at end of file diff --git a/source/Hands/IHands.mc b/source/Hands/IHands.mc new file mode 100644 index 0000000..4453510 --- /dev/null +++ b/source/Hands/IHands.mc @@ -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 { + :Identifier as Object, + :CenterShift as [Float, Float], + :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 {} + } \ No newline at end of file diff --git a/source/Hands/SimpleHands.mc b/source/Hands/SimpleHands.mc new file mode 100644 index 0000000..e926c4a --- /dev/null +++ b/source/Hands/SimpleHands.mc @@ -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; + } + } +} \ No newline at end of file diff --git a/source/Utils.mc b/source/Utils.mc new file mode 100644 index 0000000..b3dd496 --- /dev/null +++ b/source/Utils.mc @@ -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]]; +} diff --git a/source/wfApp.mc b/source/wfApp.mc new file mode 100644 index 0000000..eacfada --- /dev/null +++ b/source/wfApp.mc @@ -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; +} \ No newline at end of file diff --git a/source/wfView.mc b/source/wfView.mc new file mode 100644 index 0000000..06c1640 --- /dev/null +++ b/source/wfView.mc @@ -0,0 +1,51 @@ +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, { + :Identifier => "Main", + }); + } + + // 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 { + } + +}