clock.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. #!/usr/bin/python3
  2. """
  3. Fancy clock.
  4. """
  5. import math
  6. import os
  7. import sys
  8. import time
  9. import cairo
  10. import yutani
  11. import toaru_fonts
  12. import fswait
  13. from menu_bar import MenuBarWidget, MenuEntryAction, MenuEntrySubmenu, MenuEntryDivider, MenuWindow
  14. from about_applet import AboutAppletWindow
  15. import yutani_mainloop
  16. app_name = "Clock"
  17. version = "1.0.0"
  18. _description = f"<b>{app_name} {version}</b>\n© 2017-2018 K. Lange\n\nAnalog clock widget.\n\n<color 0x0000FF>http://github.com/klange/toaruos</color>"
  19. class BasicWatchFace(object):
  20. def __init__(self):
  21. self.font = toaru_fonts.get_cairo_face()
  22. def draw_background(self, ctx, t):
  23. ctx.set_line_width(9)
  24. ctx.set_source_rgb(0,0,0)
  25. ctx.arc(0,0,100 - 10, 0, 2 * math.pi)
  26. ctx.stroke_preserve()
  27. ctx.set_source_rgb(1,1,1)
  28. ctx.fill()
  29. r2 = 100 - 9
  30. ctx.set_source_rgb(0,0,0)
  31. for i in range(12*5):
  32. theta = 2 * math.pi * (i / (12*5))
  33. if i % 5 == 0:
  34. ctx.set_line_width(2)
  35. r1 = 100 - 20
  36. else:
  37. ctx.set_line_width(0.5)
  38. r1 = 100 - 14
  39. ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
  40. ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
  41. ctx.stroke()
  42. def setup_labels(self, ctx, t):
  43. ctx.set_font_face(self.font)
  44. ctx.set_font_size(12)
  45. ctx.set_source_rgb(0,0,0)
  46. def draw_labels(self, ctx, t):
  47. ctx.save()
  48. label = "12"
  49. e = ctx.text_extents(label)
  50. ctx.move_to(-e[2]/2,-72+e[3])
  51. ctx.show_text(label)
  52. ctx.fill()
  53. label = "3"
  54. e = ctx.text_extents(label)
  55. ctx.move_to(75-e[2],e[3]/2)
  56. ctx.show_text(label)
  57. ctx.fill()
  58. label = "9"
  59. e = ctx.text_extents(label)
  60. ctx.move_to(-75,e[3]/2)
  61. ctx.show_text(label)
  62. ctx.fill()
  63. label = "6"
  64. e = ctx.text_extents(label)
  65. ctx.move_to(-e[2]/2,72)
  66. ctx.show_text(label)
  67. ctx.fill()
  68. label = "1"
  69. e = ctx.text_extents(label)
  70. ctx.move_to(39-e[2],-63+e[3])
  71. ctx.show_text(label)
  72. ctx.fill()
  73. label = "11"
  74. e = ctx.text_extents(label)
  75. ctx.move_to(-39,-63+e[3])
  76. ctx.show_text(label)
  77. ctx.fill()
  78. label = "5"
  79. e = ctx.text_extents(label)
  80. ctx.move_to(39-e[2],63)
  81. ctx.show_text(label)
  82. ctx.fill()
  83. label = "7"
  84. e = ctx.text_extents(label)
  85. ctx.move_to(-39,63)
  86. ctx.show_text(label)
  87. ctx.fill()
  88. label = "2"
  89. e = ctx.text_extents(label)
  90. ctx.move_to(63-e[2],-37+e[3])
  91. ctx.show_text(label)
  92. ctx.fill()
  93. label = "10"
  94. e = ctx.text_extents(label)
  95. ctx.move_to(-63,-37+e[3])
  96. ctx.show_text(label)
  97. ctx.fill()
  98. label = "4"
  99. e = ctx.text_extents(label)
  100. ctx.move_to(63-e[2],37)
  101. ctx.show_text(label)
  102. ctx.fill()
  103. label = "8"
  104. e = ctx.text_extents(label)
  105. ctx.move_to(-63,37)
  106. ctx.show_text(label)
  107. ctx.fill()
  108. ctx.restore()
  109. def draw_date(self, ctx, t):
  110. ctx.save()
  111. ctx.set_font_size(10)
  112. label = time.strftime('%B %e',t[0])
  113. e = ctx.text_extents(label)
  114. ctx.move_to(-e[2]/2,-30)
  115. ctx.show_text(label)
  116. ctx.fill()
  117. def draw_line(self, ctx, thickness, color, a, b, r1, r2):
  118. theta = (a / b) * 2 * math.pi
  119. ctx.set_line_width(thickness)
  120. ctx.set_source_rgb(*color)
  121. ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
  122. ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
  123. ctx.stroke()
  124. def tick(self,t):
  125. ts = t*t
  126. tc = ts*t
  127. return (0.5*tc*ts + -8*ts*ts + 20*tc + -19*ts + 7.5*t);
  128. def draw_hands(self, ctx, t):
  129. _,h,m,s = t
  130. self.draw_line(ctx,3,(0,0,0),h%12+(m+s/60)/60,12,52,-5)
  131. self.draw_line(ctx,2,(0,0,0),m+s/60,60,86,-10)
  132. _s = int(60+s-1)+self.tick(s%1)
  133. self.draw_line(ctx,1,(1,0,0),_s,60,86,-20)
  134. self.draw_line(ctx,3,(1,0,0),_s,60,-4,-16)
  135. def draw(self, ctx, t):
  136. self.draw_background(ctx,t)
  137. self.setup_labels(ctx,t)
  138. self.draw_labels(ctx,t)
  139. self.draw_date(ctx,t)
  140. self.draw_hands(ctx,t)
  141. class DarkWatchFace(BasicWatchFace):
  142. def draw_background(self, ctx, t):
  143. ctx.set_line_width(9)
  144. ctx.set_source_rgb(1,1,1)
  145. ctx.arc(0,0,100 - 10, 0, 2 * math.pi)
  146. ctx.stroke_preserve()
  147. ctx.set_source_rgb(0,0,0)
  148. ctx.fill()
  149. r2 = 100 - 9
  150. ctx.set_source_rgb(1,1,1)
  151. for i in range(12*5):
  152. theta = 2 * math.pi * (i / (12*5))
  153. if i % 5 == 0:
  154. ctx.set_line_width(2)
  155. r1 = 100 - 20
  156. else:
  157. ctx.set_line_width(0.5)
  158. r1 = 100 - 14
  159. ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
  160. ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
  161. ctx.stroke()
  162. def draw_hands(self, ctx, t):
  163. _,h,m,s = t
  164. self.draw_line(ctx,3,(1,1,1),h%12+(m+s/60)/60,12,52,-5)
  165. self.draw_line(ctx,2,(1,1,1),m+s/60,60,86,-10)
  166. _s = int(60+s-1)+self.tick(s%1)
  167. self.draw_line(ctx,1,(1,0,0),_s,60,86,-20)
  168. self.draw_line(ctx,3,(1,0,0),_s,60,-4,-16)
  169. def setup_labels(self, ctx, t):
  170. ctx.set_font_face(self.font)
  171. ctx.set_font_size(12)
  172. ctx.set_source_rgb(1,1,1)
  173. class ClockWindow(yutani.Window):
  174. base_width = 200
  175. base_height = 200
  176. def __init__(self):
  177. super(ClockWindow, self).__init__(self.base_width, self.base_height, title="Clock", icon="clock", doublebuffer=True)
  178. self.move(100,100)
  179. self.update_shape(yutani.WindowShape.THRESHOLD_CLEAR)
  180. self.menus = {}
  181. self.watchfaces = {
  182. 'Default': BasicWatchFace(),
  183. 'Dark': DarkWatchFace(),
  184. }
  185. self.watchface = self.watchfaces['Default']
  186. def draw(self):
  187. surface = self.get_cairo_surface()
  188. ctx = cairo.Context(surface)
  189. # Clear
  190. ctx.set_operator(cairo.OPERATOR_SOURCE)
  191. ctx.rectangle(0,0,self.width,self.height)
  192. ctx.set_source_rgba(0,0,0,0)
  193. ctx.fill()
  194. ctx.set_operator(cairo.OPERATOR_OVER)
  195. ctx.translate(self.width / 2, self.height / 2)
  196. ctx.scale(self.width / 200, self.height / 200)
  197. current_time = time.time()
  198. t = time.localtime(int(current_time))
  199. s = current_time % 60
  200. m = t.tm_min
  201. h = t.tm_hour
  202. self.watchface.draw(ctx,(t,h,m,s))
  203. self.flip()
  204. def finish_resize(self, msg):
  205. """Accept a resize."""
  206. if msg.width != msg.height:
  207. s = min(msg.width,msg.height)
  208. self.resize_offer(s,s)
  209. return
  210. self.resize_accept(msg.width, msg.height)
  211. self.reinit()
  212. self.draw()
  213. self.resize_done()
  214. self.flip()
  215. def exit(self, data):
  216. sys.exit(0)
  217. def about(self, data=None):
  218. AboutAppletWindow(d,f"About {app_name}","/usr/share/icons/48/clock.bmp",_description,"clock")
  219. def mouse_event(self, msg):
  220. # drag start
  221. if msg.command == yutani.MouseEvent.DOWN and msg.buttons & yutani.MouseButton.BUTTON_LEFT:
  222. self.drag_start()
  223. if msg.buttons & yutani.MouseButton.BUTTON_RIGHT:
  224. if not self.menus:
  225. def set_face(x):
  226. self.watchface = self.watchfaces[x]
  227. faces = [MenuEntryAction(x,None,set_face,x) for x in self.watchfaces.keys()]
  228. menu_entries = [
  229. MenuEntrySubmenu("Watchface",faces,icon='clock'),
  230. MenuEntryAction(f"About {app_name}","star",self.about,None),
  231. MenuEntryDivider(),
  232. MenuEntryAction("Exit","exit",self.exit,None),
  233. ]
  234. menu = MenuWindow(menu_entries,(self.x+msg.new_x,self.y+msg.new_y),root=self)
  235. def keyboard_event(self, msg):
  236. if msg.event.action != yutani.KeyAction.ACTION_DOWN:
  237. return
  238. if msg.event.key == b'q':
  239. self.exit(None)
  240. if __name__ == '__main__':
  241. yutani.Yutani()
  242. d = yutani.Decor() # Just in case.
  243. window = ClockWindow()
  244. window.draw()
  245. fds = [yutani.yutani_ctx]
  246. while 1:
  247. # Poll for events.
  248. fd = fswait.fswait(fds,20)
  249. if fd == 0:
  250. msg = yutani.yutani_ctx.poll()
  251. while msg:
  252. yutani_mainloop.handle_event(msg)
  253. msg = yutani.yutani_ctx.poll(False)
  254. window.draw()
  255. elif fd == 1:
  256. # Tick
  257. window.draw()