index.html 9.4 KB

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