index.html 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <html>
  2. <head>
  3. <meta charset="utf-8">
  4. <title>DWatch</title>
  5. <style type="text/css">
  6. body {
  7. background-color: #000;
  8. color: #fff;
  9. }
  10. #canvas{
  11. /* center on page */
  12. position: absolute;
  13. top:0; left:0; bottom:0; right:0;
  14. margin: auto;
  15. }
  16. @font-face{
  17. font-family: 'digital-font';
  18. src: url('font.ttf');
  19. }
  20. svg text{
  21. font: 28px digital-font;
  22. }
  23. /* disable text selection */
  24. svg text {
  25. cursor: default;
  26. -webkit-user-select: none;
  27. -moz-user-select: none;
  28. -ms-user-select: none;
  29. user-select: none;
  30. }
  31. </style>
  32. </head>
  33. <body>
  34. <svg version="1.1"
  35. baseProfile="full"
  36. width="222" height="236"
  37. xmlns="http://www.w3.org/2000/svg"
  38. id="canvas">
  39. <image width="222" height="236" xlink:href="watch.gif"/>
  40. <rect id="display" x="51" y="95" width="120" height="55" rx="2" fill="#DCDCDC">
  41. </rect>
  42. <text id="timeText" x="111" y="126" dominant-baseline="middle" text-anchor="middle"></text>
  43. <text id="chronoText" x="111" y="126" dominant-baseline="middle" text-anchor="middle" style="display:none"></text>
  44. <text id="alarmText" x="111" y="126" dominant-baseline="middle" text-anchor="middle" style="display:none"></text>
  45. <rect id="topLeft" x="0" y="59", width="16", height="16" fill="#fff" fill-opacity="0.2" />
  46. <rect id="topRight" x="206" y="57", width="16", height="16" fill="#fff" fill-opacity="0.2" />
  47. <rect id="bottomLeft" x="0" y="158", width="16", height="16" fill="#fff" fill-opacity="0.2" />
  48. <rect id="bottomRight" x="208" y="158", width="16", height="16" fill="#fff" fill-opacity="0.2" />
  49. <image id="note" x="54" y="96" xlink:href="noteSmall.gif" style="display:none"/>
  50. </svg>
  51. <p id="log">
  52. </p>
  53. <script type="module">
  54. Number.prototype.pad = function(size) {
  55. let s = String(this);
  56. while (s.length < (size || 2)) {s = "0" + s;}
  57. return s;
  58. };
  59. class DisplayText {
  60. constructor(initial, max, element) {
  61. this.initial = initial;
  62. this.max = max;
  63. this.element = element;
  64. this.values = [...initial];
  65. this.selected = 0;
  66. this.timeout = null;
  67. this.render();
  68. }
  69. increment() {
  70. this.values[0] += 1;
  71. this.values[1] += Math.floor(this.values[0] / this.max[0]);
  72. this.values[2] += Math.floor(this.values[1] / this.max[1]);
  73. this.values[0] %= this.max[0];
  74. this.values[1] %= this.max[1];
  75. this.values[2] %= this.max[2];
  76. this.render();
  77. }
  78. reset() {
  79. this.values = [... this.initial]
  80. this.render();
  81. }
  82. render() {
  83. this.element.textContent =
  84. this.values[2].pad(2) + ':' +
  85. this.values[1].pad(2) + ':' +
  86. this.values[0].pad(2);
  87. }
  88. blink0() {
  89. this.element.textContent =
  90. (this.selected === 2 ? "__" : this.values[2].pad(2)) + ':' +
  91. (this.selected === 1 ? "__" : this.values[1].pad(2)) + ':' +
  92. (this.selected === 0 ? "__" : this.values[0].pad(2));
  93. this.timeout = setTimeout(this.blink1.bind(this), 500);
  94. }
  95. blink1() {
  96. this.render();
  97. this.timeout = setTimeout(this.blink0.bind(this), 500);
  98. }
  99. selectionStart() {
  100. this.selected = 2;
  101. this.blink0();
  102. }
  103. selectionStop() {
  104. clearTimeout(this.timeout);
  105. this.render();
  106. }
  107. selectionNext() {
  108. this.selected -= 1;
  109. if (this.selected < 0)
  110. this.selected = 2;
  111. clearTimeout(this.timeout);
  112. this.blink0();
  113. }
  114. selectionIncrease() {
  115. this.values[this.selected] += 1;
  116. this.values[this.selected] %= this.max[this.selected];
  117. clearTimeout(this.timeout);
  118. this.blink1();
  119. }
  120. show() {
  121. this.element.style.display = "";
  122. }
  123. hide() {
  124. this.element.style.display = "none";
  125. }
  126. isVisible() {
  127. return this.element.style.display !== "none";
  128. }
  129. }
  130. (async () => {
  131. const startedAt = new Date()
  132. const timeZero = +startedAt;
  133. const realtime = () => +new Date() - timeZero;
  134. const [rust, outputhandler] = await Promise.all([
  135. import("./wasm/pkg/dwatch.js"),
  136. import("./wasm/outputhandler.js"),
  137. ]);
  138. const memory = await rust.default();
  139. let purposefully_behind = 0;
  140. const elementNote = document.getElementById("note");
  141. const elementDisplay = document.getElementById("display");
  142. // Time is equal to wall-clock time
  143. const time = new DisplayText([startedAt.getSeconds(), startedAt.getMinutes(), startedAt.getHours()], [60, 60, 24], document.getElementById("timeText"));
  144. // Chrono is zero
  145. const chrono = new DisplayText([0, 0, 0], [100, 60, 100], document.getElementById("chronoText"));
  146. const alarm = new DisplayText([startedAt.getSeconds(), startedAt.getMinutes(), startedAt.getHours()], [60, 60, 24], document.getElementById("alarmText"));
  147. // Alarm is always equal to time + 10 seconds (makes it easier to test the alarm)
  148. for (let i=0; i<10; i++) {
  149. alarm.increment();
  150. }
  151. const onVisible = (objs, operation) => {
  152. objs.forEach(obj => {
  153. if (obj.isVisible()) {
  154. obj[operation]();
  155. }
  156. })
  157. }
  158. const outHandler = new outputhandler.OutputHandler(outEvent => {
  159. switch (outEvent) {
  160. case rust.OutEvent.E_setAlarm:
  161. // console.log("setAlarm");
  162. if (elementNote.style.display === "") {
  163. elementNote.style.display = "none";
  164. } else {
  165. elementNote.style.display = "";
  166. }
  167. break;
  168. case rust.OutEvent.E_setIndiglo:
  169. // console.log("setIndiglo");
  170. elementDisplay.setAttribute("fill", "#96DCFA");
  171. break;
  172. case rust.OutEvent.E_unsetIndiglo:
  173. // console.log("unsetIndiglo");
  174. elementDisplay.setAttribute("fill", "#DCDCDC");
  175. break;
  176. case rust.OutEvent.E_startSelection:
  177. // console.log("startSelection");
  178. onVisible([time, alarm], "selectionStart")
  179. break;
  180. case rust.OutEvent.E_selectNext:
  181. // console.log("selectNext");
  182. onVisible([time, alarm], "selectionNext")
  183. break;
  184. case rust.OutEvent.E_stopSelection:
  185. // console.log("stopSelection");
  186. onVisible([time, alarm], "selectionStop")
  187. break;
  188. case rust.OutEvent.E_increaseSelection:
  189. // console.log("increaseSelection");
  190. onVisible([time, alarm], "selectionIncrease")
  191. break;
  192. case rust.OutEvent.E_refreshTimeDisplay:
  193. // console.log("refreshTimeDisplay");
  194. chrono.hide();
  195. alarm.hide();
  196. time.show();
  197. break;
  198. case rust.OutEvent.E_refreshChronoDisplay:
  199. // console.log("refreshChronoDisplay");
  200. time.hide();
  201. alarm.hide();
  202. chrono.show();
  203. break;
  204. case rust.OutEvent.E_refreshAlarmDisplay:
  205. // console.log("refreshAlarmDisplay");
  206. time.hide();
  207. chrono.hide();
  208. alarm.show();
  209. break;
  210. case rust.OutEvent.E_increaseTimeByOne:
  211. // console.log("increaseTimeByOne");
  212. time.increment();
  213. break;
  214. case rust.OutEvent.E_increaseChronoByOne:
  215. // console.log("increaseChronoByOne");
  216. chrono.increment();
  217. break;
  218. case rust.OutEvent.E_resetChrono:
  219. // console.log("resetChrono");
  220. chrono.reset();
  221. break;
  222. case rust.OutEvent.E_checkTime:
  223. // console.log("checkTime");
  224. for (let i=0; i<3; i++) {
  225. if (alarm.values[i] !== time.values[i])
  226. return;
  227. }
  228. setTimeout(() => {
  229. rust.add_event(handle, realtime() - simtime, rust.InEvent.E_alarmStart);
  230. wakeup();
  231. }, 0);
  232. break;
  233. }
  234. });
  235. // (Soft) Real-time simulation
  236. const handle = rust.setup(outHandler);
  237. let timeout;
  238. let simtime;
  239. const wakeup = () => {
  240. clearTimeout(timeout);
  241. const status = rust.run_until(handle, realtime() + purposefully_behind, outHandler);
  242. simtime = status.simtime;
  243. if (status.reschedule) {
  244. let sleep_duration = status.at - realtime();
  245. if (sleep_duration < 0) {
  246. purposefully_behind = sleep_duration;
  247. sleep_duration = 0;
  248. }
  249. else {
  250. purposefully_behind = 0;
  251. }
  252. timeout = setTimeout(wakeup, sleep_duration);
  253. }
  254. }
  255. wakeup();
  256. // Send input events
  257. ["topLeft", "topRight", "bottomLeft", "bottomRight"].forEach(button => {
  258. document.getElementById(button).onmousedown = () => {
  259. // console.log(button + "Pressed");
  260. const nextWakeup = rust.add_event(handle, realtime() - simtime, rust.InEvent["E_"+button+"Pressed"]);
  261. wakeup();
  262. }
  263. document.getElementById(button).onmouseup = () => {
  264. // console.log(button + "Released");
  265. const nextWakeup = rust.add_event(handle, realtime() - simtime, rust.InEvent["E_"+button+"Released"]);
  266. wakeup();
  267. }
  268. })
  269. })();
  270. </script>
  271. </body>
  272. </html>