Forum Archive

Analog Clock Example to ui Example - help

Phuket2

I was interested in making a type of world display in ui. Of course a digital version not so challenging esp. with the new update method for custom views. I statered butchering omz's analog Example to get it to draw inside a custom ui.View class. I just don't understand the math well enough. I know basic for some, just not me. I am just guessing at the transformations I have to do. One thing I also should have done was use an ImageContext, I was getting to that.

Anyway, I thought maybe a member here a little bored on a Sunday with a few mins :) might be interested in doing the conversion properly. Maybe add a few properties for some of the colours/fonts etc...

I think if it can be written nice and clean to PEP8 @omz might consider adding it to his examples as it would show the use of the new update method, imagecontexts, custom drawing.

Besides that we get a neat Analog Clock class we can use. Anyway, was just an idea.

I have put my butchered code below along with @omz Example. I know it's crappy, just to give an idea the direction I was going. I think the overall direction is ok. It's just about getting the drawing done correctly. The datetime calcs probably should have their own method. As I say, I was just trying to see if I could convert it.

Oh, I think it's important that it's resizable. Eg, not just fullscreen. This way you can present many copies of the clock into custom views on a ui.View for example.

Ok, will be interesting to see if anyone takes up the challenge. I have a feeling if you know what you are doing this is around 10-15mins work. I wish I could just do it.

'''
A simple analog clock made of ShapeNodes.
'''
import ui
from scene import *
from math import pi, sin, cos
from datetime import datetime

class Clock2(ui.View):
    def __init__(self):
        self.width = 600
        self.height = 600
        self.hands = [(),(),()]
        self.update_interval = 1

    def update(self):
        self.set_needs_display()
        #print('update')

    def draw(self):
        r = min(self.width, self.height)/2 * 0.9

        circle = ui.Path.oval(0, 0, r*2, r*2)
        circle.line_width = 6

        shadow = ('black', 0, 0, 15)
        ui.set_shadow(*shadow)
        ui.set_color('silver')
        #self.face = ShapeNode(circle, 'white', 'silver', shadow=shadow)
        circle.fill()
        circle.stroke()

        for i in range(12):
            label = LabelNode(str(i+1), font=('HelveticaNeue-UltraLight', 0.2*r))
            label.color = 'black'
            a = 2 * pi * (i+1)/12.0
            #label.position = sin(a)*(r*0.85), cos(a)*(r*0.85)
            label.position = sin(a)*(r*0.85), cos(a)*(r*0.85)
            #print(label.position)
            ui.draw_string(str(i+1), rect=(r+ label.position[0], r +label.position[1], 0, 0), font=('<system>', 18), color='black', alignment=ui.ALIGN_CENTER, line_break_mode=ui.LB_WORD_WRAP)

        #self.hands = []
        #hand_attrs = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        #self.hands = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        t = datetime.now()
        tick = -2 * pi / 60.0
        seconds = t.second + t.microsecond/1000000.0
        minutes = t.minute + seconds/60.0
        hours = (t.hour % 12) + minutes/60.0
        self.hands[0] = 5 * tick * hours
        self.hands[1] = tick * minutes
        self.hands[2] = tick * seconds
        #print(type(self.hands))
        for l, w, color in self.hands:
            #shape = ShapeNode(ui.Path.rounded_rect(0, 0, w, l, w/2), color)
            shape = ui.Path.rounded_rect(0, 0, w, l, w/2)
            #shape.anchor_point = (0.5, 0)
            shape.stroke()
            #self.hands.append(shape)
            #self.face.add_child(shape)



class Clock (Scene):
    def setup(self):
        print(self.size)
        r = min(self.size)/2 * 0.9
        circle = ui.Path.oval(0, 0, r*2, r*2)
        circle.line_width = 6
        shadow = ('black', 0, 0, 15)
        self.face = ShapeNode(circle, 'white', 'silver', shadow=shadow)
        self.add_child(self.face)
        for i in range(12):
            label = LabelNode(str(i+1), font=('HelveticaNeue-UltraLight', 0.2*r))
            label.color = 'black'
            a = 2 * pi * (i+1)/12.0
            label.position = sin(a)*(r*0.85), cos(a)*(r*0.85)
            self.face.add_child(label)
        self.hands = []
        hand_attrs = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        for l, w, color in hand_attrs:
            shape = ShapeNode(ui.Path.rounded_rect(0, 0, w, l, w/2), color)
            shape.anchor_point = (0.5, 0)
            self.hands.append(shape)
            self.face.add_child(shape)
        self.face.add_child(ShapeNode(ui.Path.oval(0, 0, 15, 15), 'black'))
        self.did_change_size()

    def did_change_size(self):
        self.face.position = self.size/2

    def update(self):
        t = datetime.now()
        tick = -2 * pi / 60.0
        seconds = t.second + t.microsecond/1000000.0
        minutes = t.minute + seconds/60.0
        hours = (t.hour % 12) + minutes/60.0
        self.hands[0].rotation = 5 * tick * hours
        self.hands[1].rotation = tick * minutes
        self.hands[2].rotation = tick * seconds

if __name__ == '__main__':
    is_scene_clock = False
    if is_scene_clock:
        run(Clock())
    else:
        v = Clock2()
        v.present('sheet''''
A simple analog clock made of ShapeNodes.
'''
import ui
from scene import *
from math import pi, sin, cos
from datetime import datetime

class Clock2(ui.View):
    def __init__(self):
        self.width = 600
        self.height = 600
        self.hands = [(),(),()]
        self.update_interval = 1

    def update(self):
        self.set_needs_display()
        #print('update')

    def draw(self):
        r = min(self.width, self.height)/2 * 0.9

        circle = ui.Path.oval(0, 0, r*2, r*2)
        circle.line_width = 6

        shadow = ('black', 0, 0, 15)
        ui.set_shadow(*shadow)
        ui.set_color('silver')
        #self.face = ShapeNode(circle, 'white', 'silver', shadow=shadow)
        circle.fill()
        circle.stroke()

        for i in range(12):
            label = LabelNode(str(i+1), font=('HelveticaNeue-UltraLight', 0.2*r))
            label.color = 'black'
            a = 2 * pi * (i+1)/12.0
            #label.position = sin(a)*(r*0.85), cos(a)*(r*0.85)
            label.position = sin(a)*(r*0.85), cos(a)*(r*0.85)
            #print(label.position)
            ui.draw_string(str(i+1), rect=(r+ label.position[0], r +label.position[1], 0, 0), font=('<system>', 18), color='black', alignment=ui.ALIGN_CENTER, line_break_mode=ui.LB_WORD_WRAP)

        #self.hands = []
        #hand_attrs = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        #self.hands = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        t = datetime.now()
        tick = -2 * pi / 60.0
        seconds = t.second + t.microsecond/1000000.0
        minutes = t.minute + seconds/60.0
        hours = (t.hour % 12) + minutes/60.0
        self.hands[0] = 5 * tick * hours
        self.hands[1] = tick * minutes
        self.hands[2] = tick * seconds
        #print(type(self.hands))
        for l, w, color in self.hands:
            #shape = ShapeNode(ui.Path.rounded_rect(0, 0, w, l, w/2), color)
            shape = ui.Path.rounded_rect(0, 0, w, l, w/2)
            #shape.anchor_point = (0.5, 0)
            shape.stroke()
            #self.hands.append(shape)
            #self.face.add_child(shape)



class Clock (Scene):
    def setup(self):
        print(self.size)
        r = min(self.size)/2 * 0.9
        circle = ui.Path.oval(0, 0, r*2, r*2)
        circle.line_width = 6
        shadow = ('black', 0, 0, 15)
        self.face = ShapeNode(circle, 'white', 'silver', shadow=shadow)
        self.add_child(self.face)
        for i in range(12):
            label = LabelNode(str(i+1), font=('HelveticaNeue-UltraLight', 0.2*r))
            label.color = 'black'
            a = 2 * pi * (i+1)/12.0
            label.position = sin(a)*(r*0.85), cos(a)*(r*0.85)
            self.face.add_child(label)
        self.hands = []
        hand_attrs = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        for l, w, color in hand_attrs:
            shape = ShapeNode(ui.Path.rounded_rect(0, 0, w, l, w/2), color)
            shape.anchor_point = (0.5, 0)
            self.hands.append(shape)
            self.face.add_child(shape)
        self.face.add_child(ShapeNode(ui.Path.oval(0, 0, 15, 15), 'black'))
        self.did_change_size()

    def did_change_size(self):
        self.face.position = self.size/2

    def update(self):
        t = datetime.now()
        tick = -2 * pi / 60.0
        seconds = t.second + t.microsecond/1000000.0
        minutes = t.minute + seconds/60.0
        hours = (t.hour % 12) + minutes/60.0
        self.hands[0].rotation = 5 * tick * hours
        self.hands[1].rotation = tick * minutes
        self.hands[2].rotation = tick * seconds

if __name__ == '__main__':
    is_scene_clock = False
    if is_scene_clock:
        run(Clock())
    else:
        v = Clock2()
        v.present('sheet')

enceladus

Here is the code displaying four analog clocks (no scene functions, pure ui)

import ui


from math import pi, sin, cos
from datetime import datetime


LabelNode = ui.Label
SpriteNode = ui.ImageView
class ShapeNode(ui.View):
    def __init__(self, path=None, fill_color='white', stroke_color='clear', shadow=None, *args, **kwargs):
        self.path = path
        self.fill_color = fill_color
        self.stroke_color = stroke_color
        super().__init__(*args, **kwargs)

    def draw(self):
        ui.set_color(self.fill_color)
        self.path.fill()
        ui.set_color(self.stroke_color)
        self.path.stroke()

class AnalogClock(ui.View):           
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        center_x, center_y = self.center
        self.w1 = min(self.height, self.width)
        center_x, center_y = (self.w1/2, self.w1/2)
        r = (self.w1/2) * 0.9
        #print(r)
        circle = ui.Path.oval(0, 0, r*2, r*2)
        circle.line_width = 6
        shadow = ('black', 0, 0, 15)
        frame = (center_x -r, center_y - r, 2*r, 2*r)
        self.face = ShapeNode(circle, 'white', 'silver', shadow=shadow, frame=frame )
        self.add_subview(self.face)
        for i in range(12):
            a = 2 * pi * (i+1)/12.0 -pi/2
            label = LabelNode(text='{:2d}'.format(i+1), font=('HelveticaNeue-UltraLight', 0.2*r),
                text_color='black',
                frame=(cos(a)*(r*0.85)+center_x-.1*r, sin(a)*(r*0.85)+center_y-r*.85, 2*r*.85, 2*r*.85))
            self.add_subview(label)
        self.hands = []
        self.update_interval = .1
        hand_attrs = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        for l, w, color in hand_attrs:
            shape = ShapeNode(ui.Path.rounded_rect(l-w/2, 0, w, l, w/2), color,
            frame=(center_x-l, center_y-l, 2*l, 2*l))
            #shape.anchor_point = (0.5, 0)
            self.hands.append(shape)
            self.add_subview(shape)
        self.add_subview(ShapeNode(ui.Path.oval(0, 0, 15, 15), 'black',
                        frame=(center_x-7.5, center_y-7.5, 15, 15)))

    def update(self):
        t = datetime.now()
        tick = 2 * pi / 60.0
        seconds = t.second + t.microsecond/1000000.0
        minutes = t.minute + seconds/60.0
        hours = (t.hour % 12) + minutes/60.0
        self.hands[0].transform = ui.Transform.rotation(5 * tick * hours)
        self.hands[1].transform = ui.Transform.rotation(tick * minutes)
        self.hands[2].transform = ui.Transform.rotation(tick * seconds)


v = ui.View(frame=(0,0,600,600))
v1 = AnalogClock(frame=(0,0,200,200))
v2 = AnalogClock(frame=(0,300,200,200))
v3 = AnalogClock(frame=(300,0,200,200))
v4 = AnalogClock(frame=(300,300,200,200))
v.add_subview(v1)
v.add_subview(v2)
v.add_subview(v3)
v.add_subview(v4)
v.present('sheet')

With timezone code
https://gist.github.com/19e99d748397855003afdebf32dafc3a

Phuket2

@enceladus , thanks. There appears to be a small draw problem on the clock face being squared off. I didn't try to fix it, but will look,at it. Below is your code with a minor adjustment to add time zones. There is no error checking around the tz_str passed, it has to be a valid tz string from pytz. The reason, I did not do more with this is because the strings not very nice. A better approach would be to use the arrow lib, I will do that later. Thought it was better to stick the included Libs to begin with.
But thanks again. I will spend more time looking at the code soon as I have time. I just want to add the start of time zones.

import ui
from math import pi, sin, cos
from datetime import datetime
import pytz

LabelNode = ui.Label
SpriteNode = ui.ImageView
class ShapeNode(ui.View):
    def __init__(self, path=None, fill_color='white', stroke_color='clear', shadow=None, *args, **kwargs):
        self.path = path
        self.fill_color = fill_color
        self.stroke_color = stroke_color
        super().__init__(*args, **kwargs)

    def draw(self):
        ui.set_color(self.fill_color)
        self.path.fill()
        ui.set_color(self.stroke_color)
        self.path.stroke()

class AnalogClock(ui.View):           
    def __init__(self, tz_str = None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tz_str = tz_str

        center_x, center_y = self.center
        self.w1 = min(self.height, self.width)
        center_x, center_y = (self.w1/2, self.w1/2)
        r = (self.w1/2) * 0.9
        #print(r)
        circle = ui.Path.oval(0, 0, r*2, r*2)
        circle.line_width = 6
        shadow = ('black', 0, 0, 15)
        frame = (center_x -r, center_y - r, 2*r, 2*r)
        self.face = ShapeNode(circle, 'white', 'silver', shadow=shadow, frame=frame )
        self.add_subview(self.face)
        for i in range(12):
            a = 2 * pi * (i+1)/12.0 -pi/2
            label = LabelNode(text='{:2d}'.format(i+1), font=('HelveticaNeue-UltraLight', 0.2*r),
                text_color='black',
                frame=(cos(a)*(r*0.85)+center_x-.1*r, sin(a)*(r*0.85)+center_y-r*.85, 2*r*.85, 2*r*.85))
            self.add_subview(label)
        self.hands = []
        self.update_interval = .1
        hand_attrs = [(r*0.6, 8, 'black'), (r*0.9, 8, 'black'), (r*0.9, 4, 'red')]
        for l, w, color in hand_attrs:
            shape = ShapeNode(ui.Path.rounded_rect(l-w/2, 0, w, l, w/2), color,
            frame=(center_x-l, center_y-l, 2*l, 2*l))
            #shape.anchor_point = (0.5, 0)
            self.hands.append(shape)
            self.add_subview(shape)
        self.add_subview(ShapeNode(ui.Path.oval(0, 0, 15, 15), 'black',
                        frame=(center_x-7.5, center_y-7.5, 15, 15)))

    def get_dt(self):
        # return a datetime object for the current datetime for self.tz_str time zone, otherwise 
        # the hardwares datetime setting
        return datetime.now(pytz.timezone(self.tz_str)) if self.tz_str else datetime.now()

    def update(self):
        #t = datetime.now()
        t = self.get_dt()
        tick = 2 * pi / 60.0
        seconds = t.second + t.microsecond/1000000.0
        minutes = t.minute + seconds/60.0
        hours = (t.hour % 12) + minutes/60.0
        self.hands[0].transform = ui.Transform.rotation(5 * tick * hours)
        self.hands[1].transform = ui.Transform.rotation(tick * minutes)
        self.hands[2].transform = ui.Transform.rotation(tick * seconds)


v = ui.View(frame=(0,0,600,600))
v1 = AnalogClock('US/Pacific', frame=(0,0,200,200))
v2 = AnalogClock('Asia/Bangkok', frame=(0,300,200,200))
v3 = AnalogClock('Singapore', frame=(300,0,200,200))
v4 = AnalogClock(frame=(300,300,200,200)) # should be the devices local time 
v.add_subview(v1)
v.add_subview(v2)
v.add_subview(v3)
v.add_subview(v4)
v.present('sheet')
enceladus

The gist below my post contains the code with timezone. Anyway it is almost like what you have.

Phuket2

@enceladus , sorry I missed the gist reference on your post. But yes almost identical approach for tz. Not sure if you have used arrow or not. But it would make the time zones a lot nicer. I have arrow installed, but will wait to later to use it. I will just do a little to adding a label into the clock face for the time zone and or a label added the bottom of the clock's bounding rect.

Phuket2

Oh, I should clear up a mistake/assumption I made. It appears that at least in the latest beta, the documentation states under undocumented modules that arrow ships with Pythonista.

That's nice.