Convert Watch App To Typescript
parent
f95466275e
commit
7e0acb45e9
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -1,8 +1,7 @@
|
|||
{
|
||||
"scripts": {
|
||||
"start": "react-native start",
|
||||
"android": "react-native run-android",
|
||||
"android-prebuild": "npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/"
|
||||
"dev": "react-native start",
|
||||
"android": "react-native run-android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^12.0.0",
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "focus",
|
||||
"private": "true",
|
||||
"version": "0.0.1",
|
||||
"description": "Scripts for the Focus MonoRepo.",
|
||||
"scripts": {
|
||||
"dev": "npm run dev-watch & npm run dev-companion",
|
||||
"dev-watch": "cd watch && npm run dev",
|
||||
"dev-companion": "cd companion && npm run dev"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Aurailus/Focus.git"
|
||||
},
|
||||
"keywords": [
|
||||
"watch",
|
||||
"tizen",
|
||||
"android",
|
||||
"calendar",
|
||||
"scheduling"
|
||||
],
|
||||
"author": "Auri Collings <me@auri.xyz>",
|
||||
"license": "UNLICENSED",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Aurailus/Focus/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Aurailus/Focus#readme"
|
||||
}
|
BIN
watch/Focus.wgt
BIN
watch/Focus.wgt
Binary file not shown.
|
@ -1,206 +0,0 @@
|
|||
Flora License
|
||||
|
||||
Version 1.1, April, 2013
|
||||
|
||||
http://floralicense.org/license/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and
|
||||
all other entities that control, are controlled by, or are
|
||||
under common control with that entity. For the purposes of
|
||||
this definition, "control" means (i) the power, direct or indirect,
|
||||
to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (ii) ownership of fifty percent (50%)
|
||||
or more of the outstanding shares, or (iii) beneficial ownership of
|
||||
such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation source,
|
||||
and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form,
|
||||
made available under the License, as indicated by a copyright notice
|
||||
that is included in or attached to the work (an example is provided
|
||||
in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form,
|
||||
that is based on (or derived from) the Work and for which the editorial
|
||||
revisions, annotations, elaborations, or other modifications represent,
|
||||
as a whole, an original work of authorship. For the purposes of this License,
|
||||
Derivative Works shall not include works that remain separable from,
|
||||
or merely link (or bind by name) to the interfaces of, the Work and
|
||||
Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original
|
||||
version of the Work and any modifications or additions to that Work or
|
||||
Derivative Works thereof, that is intentionally submitted to Licensor
|
||||
for inclusion in the Work by the copyright owner or by an individual or
|
||||
Legal Entity authorized to submit on behalf of the copyright owner.
|
||||
For the purposes of this definition, "submitted" means any form of
|
||||
electronic, verbal, or written communication sent to the Licensor or
|
||||
its representatives, including but not limited to communication on
|
||||
electronic mailing lists, source code control systems, and issue
|
||||
tracking systems that are managed by, or on behalf of, the Licensor
|
||||
for the purpose of discussing and improving the Work, but excluding
|
||||
communication that is conspicuously marked or otherwise designated
|
||||
in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
"Tizen Certified Platform" shall mean a software platform that complies
|
||||
with the standards set forth in the Tizen Compliance Specification
|
||||
and passes the Tizen Compliance Tests as defined from time to time
|
||||
by the Tizen Technical Steering Group and certified by the Tizen
|
||||
Association or its designated agent.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work
|
||||
solely as incorporated into a Tizen Certified Platform, where such
|
||||
license applies only to those patent claims licensable by such
|
||||
Contributor that are necessarily infringed by their Contribution(s)
|
||||
alone or by combination of their Contribution(s) with the Work solely
|
||||
as incorporated into a Tizen Certified Platform to which such
|
||||
Contribution(s) was submitted. If You institute patent litigation
|
||||
against any entity (including a cross-claim or counterclaim
|
||||
in a lawsuit) alleging that the Work or a Contribution incorporated
|
||||
within the Work constitutes direct or contributory patent infringement,
|
||||
then any patent licenses granted to You under this License for that
|
||||
Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof pursuant to the copyright license
|
||||
above, in any medium, with or without modifications, and in Source or
|
||||
Object form, provided that You meet the following conditions:
|
||||
|
||||
1. You must give any other recipients of the Work or Derivative Works
|
||||
a copy of this License; and
|
||||
2. You must cause any modified files to carry prominent notices stating
|
||||
that You changed the files; and
|
||||
3. You must retain, in the Source form of any Derivative Works that
|
||||
You distribute, all copyright, patent, trademark, and attribution
|
||||
notices from the Source form of the Work, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works; and
|
||||
4. If the Work includes a "NOTICE" text file as part of its distribution,
|
||||
then any Derivative Works that You distribute must include a readable
|
||||
copy of the attribution notices contained within such NOTICE file,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works, in at least one of the following places:
|
||||
within a NOTICE text file distributed as part of the Derivative Works;
|
||||
within the Source form or documentation, if provided along with the
|
||||
Derivative Works; or, within a display generated by the Derivative Works,
|
||||
if and wherever such third-party notices normally appear.
|
||||
The contents of the NOTICE file are for informational purposes only
|
||||
and do not modify the License. You may add Your own attribution notices
|
||||
within Derivative Works that You distribute, alongside or as an addendum
|
||||
to the NOTICE text from the Work, provided that such additional attribution
|
||||
notices cannot be construed as modifying the License. You may add Your own
|
||||
copyright statement to Your modifications and may provide additional or
|
||||
different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works
|
||||
as a whole, provided Your use, reproduction, and distribution of
|
||||
the Work otherwise complies with the conditions stated in this License
|
||||
and your own copyright statement or terms and conditions do not conflict
|
||||
the conditions stated in the License including section 3.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Flora License to your work
|
||||
|
||||
To apply the Flora License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2015 Samsung Electronics Co., Ltd.
|
||||
|
||||
Licensed under the Flora License, Version 1.1 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://floralicense.org/license/
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved.
|
||||
Except as noted, this software is licensed under Flora License, Version 1.
|
||||
Please, see the LICENSE.Flora file for Flora License, Version 1 terms and conditions.
|
|
@ -0,0 +1,222 @@
|
|||
"use strict";
|
||||
var theme = {
|
||||
notches: 3,
|
||||
events: true,
|
||||
minutes: true,
|
||||
glow: true,
|
||||
glow_a: '#e838ff',
|
||||
glow_b: 'rgba(255, 38, 56, 0.6)',
|
||||
glow_c: 'rgba(179, 38, 255, 1)',
|
||||
};
|
||||
var image_loaded = false;
|
||||
var window_loaded = false;
|
||||
window.onload = function () {
|
||||
window_loaded = true;
|
||||
if (image_loaded)
|
||||
init();
|
||||
};
|
||||
var glow_image = document.createElement('img');
|
||||
glow_image.src = 'res/glow.png';
|
||||
glow_image.onload = function () {
|
||||
image_loaded = true;
|
||||
if (window_loaded)
|
||||
init();
|
||||
};
|
||||
var glow_canvas = document.createElement('canvas');
|
||||
glow_canvas.width = 128;
|
||||
glow_canvas.height = 128;
|
||||
var glow_ctx = glow_canvas.getContext('2d');
|
||||
function init() {
|
||||
var canvas = document.querySelector('canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
canvas.width = document.body.clientWidth;
|
||||
canvas.height = canvas.width;
|
||||
var clockRadius = document.body.clientWidth / 2;
|
||||
var rot = 0;
|
||||
var ambient = false;
|
||||
var EVENT_WIDTH = 22;
|
||||
var EVENT_BUFFER = theme.notches === 0 ? 2 : 14;
|
||||
var TITLE_BUFFER = 2;
|
||||
var TITLE_SIZE = 16;
|
||||
var NOTCH_BUFFER = 2;
|
||||
var DATE_SIZE = 20;
|
||||
var COLOR_LIGHT_DIM = 'rgba(65, 199, 232, 0.15)';
|
||||
var COLOR_LIGHT_OVERLAY = 'rgba(65, 199, 232, 0.33)';
|
||||
var COLOR_MINUTE_HAND = 'rgba(65, 199, 232, 0.8)';
|
||||
var degToRad = function (deg) { return deg * (Math.PI / 180); };
|
||||
var createDate = function (hour, minute) {
|
||||
var date = new Date();
|
||||
date.setHours(hour, minute, 0);
|
||||
return date;
|
||||
};
|
||||
var events = [
|
||||
{ start: createDate(9, 30), end: createDate(11, 20), color: '#33B679', title: 'PAAS' },
|
||||
{ start: createDate(12, 30), end: createDate(1, 20), color: '#33B679', title: 'STAT' },
|
||||
{ start: createDate(1, 30), end: createDate(2, 20), color: '#33B679', title: 'MATH' }
|
||||
];
|
||||
function getTime() {
|
||||
try {
|
||||
return tizen.time.getCurrentDateTime();
|
||||
}
|
||||
catch (e) {
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
function drawCircle(angle, distance, width, color) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(angle);
|
||||
ctx.beginPath();
|
||||
ctx.arc(distance, 0, width / 2, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
}
|
||||
function drawRoundedRect(startDistance, endDistance, angle, width, fill, stroke, strokeWidth) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(angle);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-width / 2, startDistance);
|
||||
ctx.lineTo(-width / 2, endDistance);
|
||||
ctx.quadraticCurveTo(-width / 2, endDistance + width / 1.5, 0, endDistance + width / 1.5);
|
||||
ctx.quadraticCurveTo(width / 2, endDistance + width / 1.5, width / 2, endDistance);
|
||||
ctx.lineTo(width / 2, startDistance);
|
||||
ctx.quadraticCurveTo(width / 2, startDistance - width / 1.5, 0, startDistance - width / 1.5);
|
||||
ctx.quadraticCurveTo(-width / 2, startDistance - width / 1.5, -width / 2, startDistance);
|
||||
ctx.closePath();
|
||||
if (fill) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
}
|
||||
if (stroke && strokeWidth) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.lineWidth = strokeWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
function drawEvent(event) {
|
||||
var startAngle = degToRad(((event.start.getHours() % 12) +
|
||||
event.start.getMinutes() / 60) / 12 * 360 + EVENT_WIDTH / 6 - 90);
|
||||
var endAngle = degToRad(((event.end.getHours() % 12) +
|
||||
event.end.getMinutes() / 60) / 12 * 360 - EVENT_WIDTH / 8 - 90);
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = COLOR_LIGHT_DIM;
|
||||
ctx.arc(0, 0, clockRadius - EVENT_BUFFER, startAngle, endAngle, false);
|
||||
var controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
var controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
var endPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
var endPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosX = Math.cos(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosY = Math.sin(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
ctx.arc(0, 0, clockRadius - EVENT_BUFFER - EVENT_WIDTH, endAngle, startAngle, true);
|
||||
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
endPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
endPosX = Math.cos(startAngle) * (clockRadius - EVENT_BUFFER);
|
||||
endPosY = Math.sin(startAngle) * (clockRadius - EVENT_BUFFER);
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
ctx.restore();
|
||||
drawCircle(startAngle, clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2, EVENT_WIDTH - 8, event.color);
|
||||
ctx.font = "900 " + TITLE_SIZE + "px Arial sans-serif";
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'center';
|
||||
var currentAngle = startAngle + degToRad(96);
|
||||
for (var i = 0; i < event.title.length; i++) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(currentAngle);
|
||||
ctx.fillText(event.title[i], 0, -(clockRadius - EVENT_BUFFER - TITLE_BUFFER));
|
||||
ctx.restore();
|
||||
if (i < event.title.length - 1)
|
||||
currentAngle += ctx.measureText(event.title[i]).width * 0.0065 / 2
|
||||
+ ctx.measureText(event.title[i + 1]).width * 0.0065 / 2;
|
||||
}
|
||||
}
|
||||
function drawGlow(offsetX, offsetY, scaleMult, color) {
|
||||
glow_ctx.fillStyle = color;
|
||||
glow_ctx.globalCompositeOperation = 'source-over';
|
||||
glow_ctx.fillRect(0, 0, glow_canvas.width, glow_canvas.height);
|
||||
glow_ctx.globalCompositeOperation = 'destination-in';
|
||||
glow_ctx.drawImage(glow_image, 0, 0);
|
||||
var scale = 240 * scaleMult;
|
||||
ctx.drawImage(glow_canvas, canvas.width / 2 + offsetX - scale / 2, canvas.height / 2 + offsetY - scale / 2, scale, scale);
|
||||
}
|
||||
function drawWatchFace() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
var time = getTime();
|
||||
ctx.font = "900 " + DATE_SIZE + "px Arial sans-serif";
|
||||
ctx.fillStyle = '#aaa';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'][time.getDay()] + " " + time.getDate(), clockRadius, clockRadius + clockRadius / 1.75);
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillRect(clockRadius - 1 * DATE_SIZE, clockRadius + clockRadius / 1.75 + 2, 2 * DATE_SIZE, 2);
|
||||
if (!ambient) {
|
||||
rot = (rot + 1) % 360;
|
||||
var scaleA = 1 + (Math.sin(degToRad(rot + 180)) / 6);
|
||||
var scaleB = 1 + (Math.cos(degToRad(rot)) / 6);
|
||||
var scaleC = 1.3 + (Math.cos(degToRad(rot - 90)) / 6);
|
||||
var offsetX = Math.cos(degToRad(rot + 90)) * 50 + (1 - scaleA);
|
||||
var offsetY = Math.sin(degToRad(rot + 90)) * 50 + (1 - scaleB);
|
||||
if (theme.glow) {
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
drawGlow(-offsetX, -offsetY, scaleA, theme.glow_b);
|
||||
drawGlow(0, 0, scaleC, theme.glow_a);
|
||||
drawGlow(offsetX, offsetY, scaleB, theme.glow_c);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
if (theme.notches) {
|
||||
for (var i = 0; i < 12 * 5; i++) {
|
||||
if (i % 15 === 0)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 8, COLOR_MINUTE_HAND);
|
||||
else if (i % 5 === 0 && theme.notches >= 2)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 6, COLOR_LIGHT_OVERLAY);
|
||||
else if (theme.notches >= 3)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 4, COLOR_LIGHT_DIM);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (theme.events) {
|
||||
for (var _i = 0, events_1 = events; _i < events_1.length; _i++) {
|
||||
var event_1 = events_1[_i];
|
||||
drawEvent(event_1);
|
||||
}
|
||||
}
|
||||
drawCircle(0, 0, 36, COLOR_LIGHT_OVERLAY);
|
||||
drawCircle(0, 0, 20, '#fff');
|
||||
if (theme.minutes) {
|
||||
var minuteAngle = degToRad((time.getMinutes() + time.getSeconds() / 60) / 60 * 360 - 180);
|
||||
drawRoundedRect(34, clockRadius - 64, minuteAngle, 16, COLOR_MINUTE_HAND, undefined, undefined);
|
||||
}
|
||||
var hourAngle = degToRad(((time.getHours() % 12) + time.getMinutes() / 60) / 12 * 360 - 180);
|
||||
drawRoundedRect(36, clockRadius - 80, hourAngle, 16, 'rgba(0, 0, 0, 0.15)', '#fff', 4);
|
||||
if (!ambient)
|
||||
setTimeout(function () { return window.requestAnimationFrame(drawWatchFace); }, 1000 / 10);
|
||||
}
|
||||
window.requestAnimationFrame(drawWatchFace);
|
||||
window.addEventListener('timetick', drawWatchFace);
|
||||
window.addEventListener('ambientmodechanged', function (e) {
|
||||
ambient = e.detail.ambientMode;
|
||||
drawWatchFace();
|
||||
});
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (!document.hidden)
|
||||
drawWatchFace();
|
||||
});
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #fff;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
#container {
|
||||
display: -webkit-flex;
|
||||
-webkit-align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
|
@ -1,17 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no' />
|
||||
<title>AmbientWatch</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<title>AmbientWatch</title>
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
<script src='build/main.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
313
watch/js/app.js
313
watch/js/app.js
|
@ -1,313 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
(() => {
|
||||
let theme = {
|
||||
/** 0: none, 1: quarter, 2: hours, 3: hours + minute */
|
||||
notches: 3,
|
||||
/** False: don't display, True: display */
|
||||
events: true,
|
||||
/** False: don't show minute hand, True: show minute hand */
|
||||
minutes: true,
|
||||
/** False: don't show center glow, True: show center glow */
|
||||
glow: true,
|
||||
/** Center glow color. */
|
||||
glow_a: '#4294ff',
|
||||
/** Moving glow 1 color. */
|
||||
glow_b: 'rgba(5, 124, 242, 0.6)',
|
||||
/** Moving glow 2 color. */
|
||||
glow_c: 'rgba(198, 0, 237, 1)',
|
||||
// /** Center glow color. */
|
||||
// glow_a: '#e838ff',
|
||||
// /** Moving glow 1 color. */
|
||||
// glow_b: 'rgba(255, 38, 56, 0.6)',
|
||||
// /** Moving glow 2 color. */
|
||||
// glow_c: 'rgba(179, 38, 255, 1)',
|
||||
}
|
||||
|
||||
let image_loaded = false;
|
||||
let window_loaded = false;
|
||||
|
||||
window.onload = () => {
|
||||
window_loaded = true;
|
||||
if (image_loaded) init();
|
||||
};
|
||||
|
||||
const glow_image = document.createElement('img');
|
||||
glow_image.src = 'res/glow.png';
|
||||
glow_image.onload = () => {
|
||||
image_loaded = true;
|
||||
if (window_loaded) init();
|
||||
};
|
||||
|
||||
const glow_canvas = document.createElement('canvas');
|
||||
glow_canvas.width = 128;
|
||||
glow_canvas.height = 128;
|
||||
const glow_ctx = glow_canvas.getContext('2d');
|
||||
|
||||
function init() {
|
||||
const canvas = document.querySelector('#canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = document.body.clientWidth;
|
||||
canvas.height = canvas.width;
|
||||
const clockRadius = document.body.clientWidth / 2;
|
||||
|
||||
let rot = 0;
|
||||
let ambient = false;
|
||||
|
||||
const EVENT_WIDTH = 22;
|
||||
let EVENT_BUFFER = theme.notches === 0 ? 2 : 14;
|
||||
const TITLE_BUFFER = 2;
|
||||
const TITLE_SIZE = 16;
|
||||
const NOTCH_BUFFER = 2;
|
||||
const DATE_SIZE = 20;
|
||||
|
||||
const COLOR_LIGHT_DIM = 'rgba(65, 199, 232, 0.15)';
|
||||
const COLOR_LIGHT_OVERLAY = 'rgba(65, 199, 232, 0.33)';
|
||||
const COLOR_MINUTE_HAND = 'rgba(65, 199, 232, 0.8)';
|
||||
|
||||
const degToRad = (deg) => deg * (Math.PI / 180);
|
||||
|
||||
const createDate = (hour, minute) => {
|
||||
const date = new Date();
|
||||
date.setHours(hour, minute, 0);
|
||||
return date;
|
||||
};
|
||||
|
||||
const events = [
|
||||
{ start: createDate(9, 30), end: createDate(11, 20), color: '#33B679', title: 'PAAS' },
|
||||
{ start: createDate(12, 30), end: createDate(1, 20), color: '#33B679', title: 'STAT' },
|
||||
{ start: createDate(1, 30), end: createDate(2, 20), color: '#33B679', title: 'MATH' }
|
||||
// { start: createDate(9, 30), end: createDate(11, 20), color: '#59acff', title: 'Work' },
|
||||
// { start: createDate(12, 30), end: createDate(1, 20), color: '#59acff', title: 'Walk' },
|
||||
// { start: createDate(1, 30), end: createDate(2, 20), color: '#59acff', title: 'Bike' }
|
||||
];
|
||||
|
||||
function getTime() {
|
||||
try { return tizen.time.getCurrentDateTime(); }
|
||||
catch (e) { return new Date(); }
|
||||
}
|
||||
|
||||
function drawCircle(angle, distance, width, color) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(angle)
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(distance, 0, width / 2, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// function drawLine(startDistance, endDistance, angle, width, color) {
|
||||
// ctx.save();
|
||||
// ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
// ctx.rotate(degToRad(angle + 90));
|
||||
|
||||
// ctx.beginPath();
|
||||
// ctx.lineWidth = width;
|
||||
// ctx.strokeStyle = color;
|
||||
// ctx.moveTo(startDistance, 0);
|
||||
// ctx.lineTo(endDistance, 0);
|
||||
// ctx.stroke();
|
||||
// ctx.closePath();
|
||||
|
||||
// ctx.restore();
|
||||
// }
|
||||
|
||||
function drawRoundedRect(startDistance, endDistance, angle, width, fill, stroke, strokeWidth) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(angle);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-width / 2, startDistance);
|
||||
ctx.lineTo(-width / 2, endDistance);
|
||||
ctx.quadraticCurveTo(-width / 2, endDistance + width / 1.5, 0, endDistance + width / 1.5);
|
||||
ctx.quadraticCurveTo(width / 2, endDistance + width / 1.5, width / 2, endDistance);
|
||||
ctx.lineTo(width / 2, startDistance);
|
||||
ctx.quadraticCurveTo(width / 2, startDistance - width / 1.5, 0, startDistance - width / 1.5);
|
||||
ctx.quadraticCurveTo(-width / 2, startDistance - width / 1.5, -width / 2, startDistance);
|
||||
ctx.closePath();
|
||||
|
||||
if (fill) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
if (stroke && strokeWidth) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.lineWidth = strokeWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
function drawEvent(event) {
|
||||
const startAngle = degToRad(((event.start.getHours() % 12) +
|
||||
event.start.getMinutes() / 60) / 12 * 360 + EVENT_WIDTH / 6 - 90);
|
||||
const endAngle = degToRad(((event.end.getHours() % 12) +
|
||||
event.end.getMinutes() / 60) / 12 * 360 - EVENT_WIDTH / 8 - 90);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = COLOR_LIGHT_DIM;
|
||||
|
||||
ctx.arc(0, 0, clockRadius - EVENT_BUFFER, startAngle, endAngle, false);
|
||||
|
||||
let controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
let controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
let endPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
let endPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosX = Math.cos(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosY = Math.sin(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
ctx.arc(0, 0, clockRadius - EVENT_BUFFER - EVENT_WIDTH, endAngle, startAngle, true);
|
||||
|
||||
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
endPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
endPosX = Math.cos(startAngle) * (clockRadius - EVENT_BUFFER);
|
||||
endPosY = Math.sin(startAngle) * (clockRadius - EVENT_BUFFER);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
drawCircle(startAngle, clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2, EVENT_WIDTH - 8, event.color);
|
||||
|
||||
ctx.font = `900 ${TITLE_SIZE}px Arial sans-serif`;
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
let currentAngle = startAngle + degToRad(96);
|
||||
for (let i = 0; i < event.title.length; i++) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(currentAngle);
|
||||
|
||||
ctx.fillText(event.title[i], 0, -(clockRadius - EVENT_BUFFER - TITLE_BUFFER));
|
||||
|
||||
ctx.restore();
|
||||
if (i < event.title.length - 1)
|
||||
currentAngle += ctx.measureText(event.title[i]).width * 0.0065 / 2
|
||||
+ ctx.measureText(event.title[i + 1]).width * 0.0065 / 2;
|
||||
}
|
||||
}
|
||||
|
||||
function drawGlow(offsetX, offsetY, scaleMult, color) {
|
||||
glow_ctx.fillStyle = color;
|
||||
glow_ctx.globalCompositeOperation = 'source-over';
|
||||
glow_ctx.fillRect(0, 0, glow_canvas.width, glow_canvas.height);
|
||||
glow_ctx.globalCompositeOperation = 'destination-in';
|
||||
glow_ctx.drawImage(glow_image, 0, 0);
|
||||
|
||||
const scale = 240 * scaleMult;
|
||||
|
||||
ctx.drawImage(glow_canvas,
|
||||
canvas.width / 2 + offsetX - scale / 2,
|
||||
canvas.height / 2 + offsetY - scale / 2,
|
||||
scale, scale);
|
||||
}
|
||||
|
||||
function drawWatchFace() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const time = getTime();
|
||||
|
||||
ctx.font = `900 ${DATE_SIZE}px Arial sans-serif`;
|
||||
ctx.fillStyle = '#aaa';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
ctx.fillText(`${['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'][time.getDay()]} ${time.getDate()}`,
|
||||
clockRadius, clockRadius + clockRadius / 1.75);
|
||||
|
||||
ctx.fillStyle = '#666'
|
||||
ctx.fillRect(clockRadius - 1 * DATE_SIZE, clockRadius + clockRadius / 1.75 + 2, 2 * DATE_SIZE, 2);
|
||||
|
||||
if (!ambient) {
|
||||
rot = (rot + 1) % 360;
|
||||
|
||||
let scaleA = 1 + (Math.sin(degToRad(rot + 180)) / 6);
|
||||
let scaleB = 1 + (Math.cos(degToRad(rot)) / 6);
|
||||
let scaleC = 1.3 + (Math.cos(degToRad(rot - 90)) / 6);
|
||||
let offsetX = Math.cos(degToRad(rot + 90)) * 50 + (1 - scaleA);
|
||||
let offsetY = Math.sin(degToRad(rot + 90)) * 50 + (1 - scaleB);
|
||||
|
||||
if (theme.glow) {
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
drawGlow(-offsetX, -offsetY, scaleA, theme.glow_b);
|
||||
drawGlow(0, 0, scaleC, theme.glow_a);
|
||||
drawGlow(offsetX, offsetY, scaleB, theme.glow_c);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
if (theme.notches) {
|
||||
for (let i = 0; i < 12 * 5; i++) {
|
||||
if (i % 15 === 0)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 8, COLOR_MINUTE_HAND)
|
||||
else if (i % 5 === 0 && theme.notches >= 2)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 6, COLOR_LIGHT_OVERLAY)
|
||||
else if (theme.notches >= 3)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 4, COLOR_LIGHT_DIM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.events) {
|
||||
for (let event of events) {
|
||||
drawEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
drawCircle(0, 0, 36, COLOR_LIGHT_OVERLAY);
|
||||
drawCircle(0, 0, 20, '#fff');
|
||||
|
||||
if (theme.minutes) {
|
||||
const minuteAngle = degToRad((time.getMinutes() + time.getSeconds() / 60) / 60 * 360 - 180);
|
||||
drawRoundedRect(34, clockRadius - 64, minuteAngle, 16, COLOR_MINUTE_HAND);
|
||||
}
|
||||
|
||||
const hourAngle = degToRad(((time.getHours() % 12) + time.getMinutes() / 60) / 12 * 360 - 180);
|
||||
drawRoundedRect(36, clockRadius - 80, hourAngle, 16, 'rgba(0, 0, 0, 0.15)', '#fff', 4);
|
||||
|
||||
if (!ambient) setTimeout(() => window.requestAnimationFrame(drawWatchFace), 1000/10);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(drawWatchFace);
|
||||
|
||||
window.addEventListener('timetick', drawWatchFace);
|
||||
|
||||
window.addEventListener('ambientmodechanged', (e) => {
|
||||
ambient = e.detail.ambientMode;
|
||||
drawWatchFace();
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) drawWatchFace();
|
||||
});
|
||||
}
|
||||
})();
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "focus-watch",
|
||||
"version": "0.0.1",
|
||||
"description": "Tizen application for the Focus Watch Face.",
|
||||
"main": "build/Main.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Aurailus/Focus.git"
|
||||
},
|
||||
"keywords": [
|
||||
"watch",
|
||||
"tizen",
|
||||
"calendar",
|
||||
"scheduling"
|
||||
],
|
||||
"nodemonConfig": {
|
||||
"watch": [
|
||||
"src"
|
||||
],
|
||||
"extensions": ".js",
|
||||
"quiet": true,
|
||||
"exec": "tsc"
|
||||
},
|
||||
"author": "Auri Collings <me@auri.xyz>",
|
||||
"license": "UNLICENSED",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Aurailus/Focus/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Aurailus/Focus#readme",
|
||||
"devDependencies": {
|
||||
"@types/tizen-common-web": "^2.0.1",
|
||||
"nodemon": "^2.0.12",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path fill="#fff" d="M6,17C6,15 10,13.9 12,13.9C14,13.9 18,15 18,17V18H6M15,9A3,3 0 0,1 12,12A3,3 0 0,1 9,9A3,3 0 0,1 12,6A3,3 0 0,1 15,9M3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5C3.89,3 3,3.9 3,5Z" /></svg>
|
Before Width: | Height: | Size: 502 B |
|
@ -0,0 +1,310 @@
|
|||
let theme = {
|
||||
/** 0: none, 1: quarter, 2: hours, 3: hours + minute */
|
||||
notches: 3,
|
||||
/** False: don't display, True: display */
|
||||
events: true,
|
||||
/** False: don't show minute hand, True: show minute hand */
|
||||
minutes: true,
|
||||
/** False: don't show center glow, True: show center glow */
|
||||
glow: true,
|
||||
/** Center glow color. */
|
||||
glow_a: '#4294ff',
|
||||
/** Moving glow 1 color. */
|
||||
glow_b: 'rgba(5, 124, 242, 0.6)',
|
||||
/** Moving glow 2 color. */
|
||||
glow_c: 'rgba(198, 0, 237, 1)',
|
||||
// /** Center glow color. */
|
||||
// glow_a: '#e838ff',
|
||||
// /** Moving glow 1 color. */
|
||||
// glow_b: 'rgba(255, 38, 56, 0.6)',
|
||||
// /** Moving glow 2 color. */
|
||||
// glow_c: 'rgba(179, 38, 255, 1)',
|
||||
}
|
||||
|
||||
let image_loaded = false;
|
||||
let window_loaded = false;
|
||||
|
||||
window.onload = () => {
|
||||
window_loaded = true;
|
||||
if (image_loaded) init();
|
||||
};
|
||||
|
||||
const glow_image = document.createElement('img');
|
||||
glow_image.src = 'res/glow.png';
|
||||
glow_image.onload = () => {
|
||||
image_loaded = true;
|
||||
if (window_loaded) init();
|
||||
};
|
||||
|
||||
const glow_canvas = document.createElement('canvas');
|
||||
glow_canvas.width = 128;
|
||||
glow_canvas.height = 128;
|
||||
const glow_ctx = glow_canvas.getContext('2d')!;
|
||||
|
||||
function init() {
|
||||
const canvas = document.querySelector('canvas')!;
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
canvas.width = document.body.clientWidth;
|
||||
canvas.height = canvas.width;
|
||||
const clockRadius = document.body.clientWidth / 2;
|
||||
|
||||
let rot = 0;
|
||||
let ambient = false;
|
||||
|
||||
const EVENT_WIDTH = 22;
|
||||
let EVENT_BUFFER = theme.notches === 0 ? 2 : 14;
|
||||
const TITLE_BUFFER = 2;
|
||||
const TITLE_SIZE = 16;
|
||||
const NOTCH_BUFFER = 2;
|
||||
const DATE_SIZE = 20;
|
||||
|
||||
const COLOR_LIGHT_DIM = 'rgba(65, 199, 232, 0.15)';
|
||||
const COLOR_LIGHT_OVERLAY = 'rgba(65, 199, 232, 0.33)';
|
||||
const COLOR_MINUTE_HAND = 'rgba(65, 199, 232, 0.8)';
|
||||
|
||||
const degToRad = (deg: number) => deg * (Math.PI / 180);
|
||||
|
||||
const createDate = (hour: number, minute: number) => {
|
||||
const date = new Date();
|
||||
date.setHours(hour, minute, 0);
|
||||
return date;
|
||||
};
|
||||
|
||||
const events = [
|
||||
{ start: createDate(9, 30), end: createDate(11, 20), color: '#33B679', title: 'PAAS' },
|
||||
{ start: createDate(12, 30), end: createDate(1, 20), color: '#33B679', title: 'STAT' },
|
||||
{ start: createDate(1, 30), end: createDate(2, 20), color: '#33B679', title: 'MATH' }
|
||||
// { start: createDate(9, 30), end: createDate(11, 20), color: '#59acff', title: 'Work' },
|
||||
// { start: createDate(12, 30), end: createDate(1, 20), color: '#59acff', title: 'Walk' },
|
||||
// { start: createDate(1, 30), end: createDate(2, 20), color: '#59acff', title: 'Bike' }
|
||||
];
|
||||
|
||||
function getTime() {
|
||||
try { return tizen.time.getCurrentDateTime(); }
|
||||
catch (e) { return new Date(); }
|
||||
}
|
||||
|
||||
function drawCircle(angle: number, distance: number, width: number, color: string) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(angle)
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(distance, 0, width / 2, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// function drawLine(startDistance, endDistance, angle, width, color) {
|
||||
// ctx.save();
|
||||
// ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
// ctx.rotate(degToRad(angle + 90));
|
||||
|
||||
// ctx.beginPath();
|
||||
// ctx.lineWidth = width;
|
||||
// ctx.strokeStyle = color;
|
||||
// ctx.moveTo(startDistance, 0);
|
||||
// ctx.lineTo(endDistance, 0);
|
||||
// ctx.stroke();
|
||||
// ctx.closePath();
|
||||
|
||||
// ctx.restore();
|
||||
// }
|
||||
|
||||
function drawRoundedRect(startDistance: number, endDistance: number, angle: number, width: number,
|
||||
fill?: string, stroke?: string, strokeWidth?: number) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(angle);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-width / 2, startDistance);
|
||||
ctx.lineTo(-width / 2, endDistance);
|
||||
ctx.quadraticCurveTo(-width / 2, endDistance + width / 1.5, 0, endDistance + width / 1.5);
|
||||
ctx.quadraticCurveTo(width / 2, endDistance + width / 1.5, width / 2, endDistance);
|
||||
ctx.lineTo(width / 2, startDistance);
|
||||
ctx.quadraticCurveTo(width / 2, startDistance - width / 1.5, 0, startDistance - width / 1.5);
|
||||
ctx.quadraticCurveTo(-width / 2, startDistance - width / 1.5, -width / 2, startDistance);
|
||||
ctx.closePath();
|
||||
|
||||
if (fill) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
if (stroke && strokeWidth) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.lineWidth = strokeWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
function drawEvent(event: any) {
|
||||
const startAngle = degToRad(((event.start.getHours() % 12) +
|
||||
event.start.getMinutes() / 60) / 12 * 360 + EVENT_WIDTH / 6 - 90);
|
||||
const endAngle = degToRad(((event.end.getHours() % 12) +
|
||||
event.end.getMinutes() / 60) / 12 * 360 - EVENT_WIDTH / 8 - 90);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = COLOR_LIGHT_DIM;
|
||||
|
||||
ctx.arc(0, 0, clockRadius - EVENT_BUFFER, startAngle, endAngle, false);
|
||||
|
||||
let controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
let controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
let endPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
let endPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
controlPosX = Math.cos(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
controlPosY = Math.sin(endAngle + degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosX = Math.cos(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosY = Math.sin(endAngle) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
ctx.arc(0, 0, clockRadius - EVENT_BUFFER - EVENT_WIDTH, endAngle, startAngle, true);
|
||||
|
||||
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH);
|
||||
endPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
endPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
controlPosX = Math.cos(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
controlPosY = Math.sin(startAngle - degToRad(4.25)) * (clockRadius - EVENT_BUFFER);
|
||||
endPosX = Math.cos(startAngle) * (clockRadius - EVENT_BUFFER);
|
||||
endPosY = Math.sin(startAngle) * (clockRadius - EVENT_BUFFER);
|
||||
|
||||
ctx.quadraticCurveTo(controlPosX, controlPosY, endPosX, endPosY);
|
||||
|
||||
ctx.fill();
|
||||
ctx.closePath();
|
||||
|
||||
ctx.restore();
|
||||
|
||||
drawCircle(startAngle, clockRadius - EVENT_BUFFER - EVENT_WIDTH / 2, EVENT_WIDTH - 8, event.color);
|
||||
|
||||
ctx.font = `900 ${TITLE_SIZE}px Arial sans-serif`;
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
let currentAngle = startAngle + degToRad(96);
|
||||
for (let i = 0; i < event.title.length; i++) {
|
||||
ctx.save();
|
||||
ctx.translate(canvas.width / 2, canvas.height / 2);
|
||||
ctx.rotate(currentAngle);
|
||||
|
||||
ctx.fillText(event.title[i], 0, -(clockRadius - EVENT_BUFFER - TITLE_BUFFER));
|
||||
|
||||
ctx.restore();
|
||||
if (i < event.title.length - 1)
|
||||
currentAngle += ctx.measureText(event.title[i]).width * 0.0065 / 2
|
||||
+ ctx.measureText(event.title[i + 1]).width * 0.0065 / 2;
|
||||
}
|
||||
}
|
||||
|
||||
function drawGlow(offsetX: number, offsetY: number, scaleMult: number, color: string) {
|
||||
glow_ctx.fillStyle = color;
|
||||
glow_ctx.globalCompositeOperation = 'source-over';
|
||||
glow_ctx.fillRect(0, 0, glow_canvas.width, glow_canvas.height);
|
||||
glow_ctx.globalCompositeOperation = 'destination-in';
|
||||
glow_ctx.drawImage(glow_image, 0, 0);
|
||||
|
||||
const scale = 240 * scaleMult;
|
||||
|
||||
ctx.drawImage(glow_canvas,
|
||||
canvas.width / 2 + offsetX - scale / 2,
|
||||
canvas.height / 2 + offsetY - scale / 2,
|
||||
scale, scale);
|
||||
}
|
||||
|
||||
function drawWatchFace() {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const time = getTime();
|
||||
|
||||
ctx.font = `900 ${DATE_SIZE}px Arial sans-serif`;
|
||||
ctx.fillStyle = '#aaa';
|
||||
ctx.textBaseline = 'bottom';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
ctx.fillText(`${['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'][time.getDay()]} ${time.getDate()}`,
|
||||
clockRadius, clockRadius + clockRadius / 1.75);
|
||||
|
||||
ctx.fillStyle = '#666'
|
||||
ctx.fillRect(clockRadius - 1 * DATE_SIZE, clockRadius + clockRadius / 1.75 + 2, 2 * DATE_SIZE, 2);
|
||||
|
||||
if (!ambient) {
|
||||
rot = (rot + 1) % 360;
|
||||
|
||||
let scaleA = 1 + (Math.sin(degToRad(rot + 180)) / 6);
|
||||
let scaleB = 1 + (Math.cos(degToRad(rot)) / 6);
|
||||
let scaleC = 1.3 + (Math.cos(degToRad(rot - 90)) / 6);
|
||||
let offsetX = Math.cos(degToRad(rot + 90)) * 50 + (1 - scaleA);
|
||||
let offsetY = Math.sin(degToRad(rot + 90)) * 50 + (1 - scaleB);
|
||||
|
||||
if (theme.glow) {
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
drawGlow(-offsetX, -offsetY, scaleA, theme.glow_b);
|
||||
drawGlow(0, 0, scaleC, theme.glow_a);
|
||||
drawGlow(offsetX, offsetY, scaleB, theme.glow_c);
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
}
|
||||
|
||||
if (theme.notches) {
|
||||
for (let i = 0; i < 12 * 5; i++) {
|
||||
if (i % 15 === 0)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 8, COLOR_MINUTE_HAND)
|
||||
else if (i % 5 === 0 && theme.notches >= 2)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 6, COLOR_LIGHT_OVERLAY)
|
||||
else if (theme.notches >= 3)
|
||||
drawCircle(degToRad(i / (12 * 5) * 360 - 90), clockRadius - NOTCH_BUFFER - 4, 4, COLOR_LIGHT_DIM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (theme.events) {
|
||||
for (let event of events) {
|
||||
drawEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
drawCircle(0, 0, 36, COLOR_LIGHT_OVERLAY);
|
||||
drawCircle(0, 0, 20, '#fff');
|
||||
|
||||
if (theme.minutes) {
|
||||
const minuteAngle = degToRad((time.getMinutes() + time.getSeconds() / 60) / 60 * 360 - 180);
|
||||
drawRoundedRect(34, clockRadius - 64, minuteAngle, 16, COLOR_MINUTE_HAND, undefined, undefined);
|
||||
}
|
||||
|
||||
const hourAngle = degToRad(((time.getHours() % 12) + time.getMinutes() / 60) / 12 * 360 - 180);
|
||||
drawRoundedRect(36, clockRadius - 80, hourAngle, 16, 'rgba(0, 0, 0, 0.15)', '#fff', 4);
|
||||
|
||||
if (!ambient) setTimeout(() => window.requestAnimationFrame(drawWatchFace), 1000/10);
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(drawWatchFace);
|
||||
|
||||
window.addEventListener('timetick', drawWatchFace);
|
||||
|
||||
window.addEventListener('ambientmodechanged', (e: any) => {
|
||||
ambient = e.detail.ambientMode;
|
||||
drawWatchFace();
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (!document.hidden) drawWatchFace();
|
||||
});
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
"target": "es5",
|
||||
"outFile": "build/main.js",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
|
||||
"typeRoots": [ "./node_modules/@types/" ],
|
||||
|
||||
"removeComments": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"./node_modules"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue