311 lines
8.6 KiB
Python
Executable File
311 lines
8.6 KiB
Python
Executable File
#!/usr/bin/python3
|
|
"""
|
|
Fancy clock.
|
|
"""
|
|
import math
|
|
import os
|
|
import sys
|
|
import time
|
|
|
|
import cairo
|
|
|
|
import yutani
|
|
import toaru_fonts
|
|
import fswait
|
|
|
|
from menu_bar import MenuBarWidget, MenuEntryAction, MenuEntrySubmenu, MenuEntryDivider, MenuWindow
|
|
from about_applet import AboutAppletWindow
|
|
|
|
import yutani_mainloop
|
|
|
|
app_name = "Clock"
|
|
version = "1.0.0"
|
|
_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>"
|
|
|
|
class BasicWatchFace(object):
|
|
|
|
def __init__(self):
|
|
self.font = toaru_fonts.get_cairo_face()
|
|
|
|
def draw_background(self, ctx, t):
|
|
ctx.set_line_width(9)
|
|
ctx.set_source_rgb(0,0,0)
|
|
ctx.arc(0,0,100 - 10, 0, 2 * math.pi)
|
|
ctx.stroke_preserve()
|
|
ctx.set_source_rgb(1,1,1)
|
|
ctx.fill()
|
|
|
|
r2 = 100 - 9
|
|
ctx.set_source_rgb(0,0,0)
|
|
for i in range(12*5):
|
|
theta = 2 * math.pi * (i / (12*5))
|
|
if i % 5 == 0:
|
|
ctx.set_line_width(2)
|
|
r1 = 100 - 20
|
|
else:
|
|
ctx.set_line_width(0.5)
|
|
r1 = 100 - 14
|
|
ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
|
|
ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
|
|
ctx.stroke()
|
|
|
|
def setup_labels(self, ctx, t):
|
|
ctx.set_font_face(self.font)
|
|
ctx.set_font_size(12)
|
|
ctx.set_source_rgb(0,0,0)
|
|
|
|
def draw_labels(self, ctx, t):
|
|
ctx.save()
|
|
label = "12"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-e[2]/2,-72+e[3])
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "3"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(75-e[2],e[3]/2)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "9"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-75,e[3]/2)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "6"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-e[2]/2,72)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
|
|
label = "1"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(39-e[2],-63+e[3])
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "11"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-39,-63+e[3])
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
|
|
label = "5"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(39-e[2],63)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "7"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-39,63)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
|
|
label = "2"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(63-e[2],-37+e[3])
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "10"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-63,-37+e[3])
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
|
|
label = "4"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(63-e[2],37)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
label = "8"
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-63,37)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
ctx.restore()
|
|
|
|
def draw_date(self, ctx, t):
|
|
ctx.save()
|
|
ctx.set_font_size(10)
|
|
label = time.strftime('%B %e',t[0])
|
|
e = ctx.text_extents(label)
|
|
ctx.move_to(-e[2]/2,-30)
|
|
ctx.show_text(label)
|
|
ctx.fill()
|
|
|
|
def draw_line(self, ctx, thickness, color, a, b, r1, r2):
|
|
theta = (a / b) * 2 * math.pi
|
|
ctx.set_line_width(thickness)
|
|
ctx.set_source_rgb(*color)
|
|
ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
|
|
ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
|
|
ctx.stroke()
|
|
|
|
def tick(self,t):
|
|
ts = t*t
|
|
tc = ts*t
|
|
return (0.5*tc*ts + -8*ts*ts + 20*tc + -19*ts + 7.5*t);
|
|
|
|
def draw_hands(self, ctx, t):
|
|
_,h,m,s = t
|
|
self.draw_line(ctx,3,(0,0,0),h%12+(m+s/60)/60,12,52,-5)
|
|
self.draw_line(ctx,2,(0,0,0),m+s/60,60,86,-10)
|
|
|
|
_s = int(60+s-1)+self.tick(s%1)
|
|
self.draw_line(ctx,1,(1,0,0),_s,60,86,-20)
|
|
self.draw_line(ctx,3,(1,0,0),_s,60,-4,-16)
|
|
|
|
def draw(self, ctx, t):
|
|
self.draw_background(ctx,t)
|
|
self.setup_labels(ctx,t)
|
|
self.draw_labels(ctx,t)
|
|
self.draw_date(ctx,t)
|
|
self.draw_hands(ctx,t)
|
|
|
|
class DarkWatchFace(BasicWatchFace):
|
|
def draw_background(self, ctx, t):
|
|
ctx.set_line_width(9)
|
|
ctx.set_source_rgb(1,1,1)
|
|
ctx.arc(0,0,100 - 10, 0, 2 * math.pi)
|
|
ctx.stroke_preserve()
|
|
ctx.set_source_rgb(0,0,0)
|
|
ctx.fill()
|
|
|
|
r2 = 100 - 9
|
|
ctx.set_source_rgb(1,1,1)
|
|
for i in range(12*5):
|
|
theta = 2 * math.pi * (i / (12*5))
|
|
if i % 5 == 0:
|
|
ctx.set_line_width(2)
|
|
r1 = 100 - 20
|
|
else:
|
|
ctx.set_line_width(0.5)
|
|
r1 = 100 - 14
|
|
ctx.move_to(math.sin(theta) * r1, -r1 * math.cos(theta))
|
|
ctx.line_to(math.sin(theta) * r2, -r2 * math.cos(theta))
|
|
ctx.stroke()
|
|
|
|
def draw_hands(self, ctx, t):
|
|
_,h,m,s = t
|
|
self.draw_line(ctx,3,(1,1,1),h%12+(m+s/60)/60,12,52,-5)
|
|
self.draw_line(ctx,2,(1,1,1),m+s/60,60,86,-10)
|
|
|
|
_s = int(60+s-1)+self.tick(s%1)
|
|
self.draw_line(ctx,1,(1,0,0),_s,60,86,-20)
|
|
self.draw_line(ctx,3,(1,0,0),_s,60,-4,-16)
|
|
|
|
def setup_labels(self, ctx, t):
|
|
ctx.set_font_face(self.font)
|
|
ctx.set_font_size(12)
|
|
ctx.set_source_rgb(1,1,1)
|
|
|
|
|
|
class ClockWindow(yutani.Window):
|
|
|
|
base_width = 200
|
|
base_height = 200
|
|
|
|
def __init__(self):
|
|
super(ClockWindow, self).__init__(self.base_width, self.base_height, title="Clock", icon="clock", doublebuffer=True)
|
|
self.move(100,100)
|
|
self.update_shape(yutani.WindowShape.THRESHOLD_CLEAR)
|
|
|
|
self.menus = {}
|
|
|
|
self.watchfaces = {
|
|
'Default': BasicWatchFace(),
|
|
'Dark': DarkWatchFace(),
|
|
}
|
|
|
|
self.watchface = self.watchfaces['Default']
|
|
|
|
|
|
def draw(self):
|
|
surface = self.get_cairo_surface()
|
|
ctx = cairo.Context(surface)
|
|
|
|
# Clear
|
|
ctx.set_operator(cairo.OPERATOR_SOURCE)
|
|
ctx.rectangle(0,0,self.width,self.height)
|
|
ctx.set_source_rgba(0,0,0,0)
|
|
ctx.fill()
|
|
|
|
ctx.set_operator(cairo.OPERATOR_OVER)
|
|
ctx.translate(self.width / 2, self.height / 2)
|
|
ctx.scale(self.width / 200, self.height / 200)
|
|
|
|
current_time = time.time()
|
|
t = time.localtime(int(current_time))
|
|
s = current_time % 60
|
|
m = t.tm_min
|
|
h = t.tm_hour
|
|
|
|
self.watchface.draw(ctx,(t,h,m,s))
|
|
|
|
self.flip()
|
|
|
|
def finish_resize(self, msg):
|
|
"""Accept a resize."""
|
|
if msg.width != msg.height:
|
|
s = min(msg.width,msg.height)
|
|
self.resize_offer(s,s)
|
|
return
|
|
self.resize_accept(msg.width, msg.height)
|
|
self.reinit()
|
|
|
|
self.draw()
|
|
self.resize_done()
|
|
self.flip()
|
|
|
|
def exit(self, data):
|
|
sys.exit(0)
|
|
|
|
def about(self, data=None):
|
|
AboutAppletWindow(d,f"About {app_name}","/usr/share/icons/48/clock.bmp",_description,"clock")
|
|
|
|
def mouse_event(self, msg):
|
|
# drag start
|
|
if msg.command == yutani.MouseEvent.DOWN and msg.buttons & yutani.MouseButton.BUTTON_LEFT:
|
|
self.drag_start()
|
|
|
|
if msg.buttons & yutani.MouseButton.BUTTON_RIGHT:
|
|
if not self.menus:
|
|
def set_face(x):
|
|
self.watchface = self.watchfaces[x]
|
|
faces = [MenuEntryAction(x,None,set_face,x) for x in self.watchfaces.keys()]
|
|
menu_entries = [
|
|
MenuEntrySubmenu("Watchface",faces,icon='clock'),
|
|
MenuEntryAction(f"About {app_name}","star",self.about,None),
|
|
MenuEntryDivider(),
|
|
MenuEntryAction("Exit","exit",self.exit,None),
|
|
]
|
|
menu = MenuWindow(menu_entries,(self.x+msg.new_x,self.y+msg.new_y),root=self)
|
|
|
|
def keyboard_event(self, msg):
|
|
if msg.event.action != yutani.KeyAction.ACTION_DOWN:
|
|
return
|
|
if msg.event.key == b'q':
|
|
self.exit(None)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
yutani.Yutani()
|
|
d = yutani.Decor() # Just in case.
|
|
|
|
window = ClockWindow()
|
|
window.draw()
|
|
|
|
fds = [yutani.yutani_ctx]
|
|
while 1:
|
|
# Poll for events.
|
|
fd = fswait.fswait(fds,20)
|
|
if fd == 0:
|
|
msg = yutani.yutani_ctx.poll()
|
|
while msg:
|
|
yutani_mainloop.handle_event(msg)
|
|
msg = yutani.yutani_ctx.poll(False)
|
|
window.draw()
|
|
elif fd == 1:
|
|
# Tick
|
|
window.draw()
|
|
|
|
|