Skip to content

Instantly share code, notes, and snippets.

@shiracamus
Created July 11, 2023 09:45
Show Gist options
  • Save shiracamus/f34c54fcdefc4eba9d977e31c8e7bac8 to your computer and use it in GitHub Desktop.
Save shiracamus/f34c54fcdefc4eba9d977e31c8e7bac8 to your computer and use it in GitHub Desktop.
import tkinter as tk
from datetime import datetime, timedelta, timezone
# サイズ設定
SEG_WIDTH = 12 # このサイズを変更するだけで全体サイズが連動して変更される
SEG_HEIGHT = SEG_WIDTH * 4
COLON_SIZE = SEG_WIDTH * 5 // 3
CANVAS_WIDTH_NUMBER = SEG_WIDTH * 5
CANVAS_WIDTH_COLON = CANVAS_WIDTH_NUMBER // 2
CANVAS_HEIGHT = CANVAS_WIDTH_NUMBER * 2 - SEG_WIDTH
CANVAS_PAD = SEG_WIDTH * 5 // 6
# 色の設定
COLOR_BG = "black"
COLOR_SEG_ON = "orange"
COLOR_SEG_OFF = "gray20"
class ColonCanvas(tk.Canvas):
WIDTH = CANVAS_WIDTH_COLON
HEIGHT = CANVAS_HEIGHT
def __init__(self, master, color_seg_on, cnf={}, **kw):
super().__init__(master, cnf, **kw)
self.color_seg_on = color_seg_on
self._build()
def _build(self):
# 横方向の中心はキャンバスの中心
center_x = self.WIDTH // 2
# 縦方向の中心はキャンバスの1/3 と 2/3の位置
for center_y in self.HEIGHT * 1 // 3, self.HEIGHT * 2 // 3:
# 正方形を作成
x1 = center_x - COLON_SIZE // 2
y1 = center_y - COLON_SIZE // 2
x2 = center_x + COLON_SIZE // 2
y2 = center_y + COLON_SIZE // 2
id_ = self.create_rectangle(x1, y1, x2, y2)
self.itemconfig(id_, fill=self.color_seg_on, width=0)
class NumberCanvas(tk.Canvas):
WIDTH = CANVAS_WIDTH_NUMBER
HEIGHT = CANVAS_HEIGHT
# 0~9の数字を表示する7セグメントの点灯(True)消灯(False)情報
SEG_ON = (
# 上, 左上, 右上, 中, 左下, 右下, 下
(True, True, True, False, True, True, True), # 0
(False, False, True, False, False, True, False), # 1
(True, False, True, True, True, False, True), # 2
(True, False, True, True, False, True, True), # 3
(False, True, True, True, False, True, False), # 4
(True, True, False, True, False, True, True), # 5
(True, True, False, True, True, True, True), # 6
(True, True, True, False, False, True, False), # 7
(True, True, True, True, True, True, True), # 8
(True, True, True, True, False, True, True) # 9
)
# 横長六角形の基準セグメントの各頂点のx座標とy座標
SEG_XS = (
-WIDTH // 2 + SEG_WIDTH, # 左上
-WIDTH // 2 + SEG_WIDTH // 2, # 左中
-WIDTH // 2 + SEG_WIDTH, # 左下
+WIDTH // 2 - SEG_WIDTH, # 右下
+WIDTH // 2 - SEG_WIDTH // 2, # 右中
+WIDTH // 2 - SEG_WIDTH, # 右上
)
SEG_YS = (
-SEG_WIDTH // 2, # 左上
0, # 左中
+SEG_WIDTH // 2, # 左下
+SEG_WIDTH // 2, # 右下
0, # 右中
-SEG_WIDTH // 2, # 右上
)
# 基準セグメント座標を7セグメントの表示位置に変換するための回転・移動情報
SEG_TRANSFORM = (
# [回転するか?, 横方向の移動量, 縦方向の移動量]
(False, WIDTH // 2, SEG_WIDTH // 2), # 横上
(True, SEG_WIDTH // 2, HEIGHT // 2 - SEG_HEIGHT // 2), # 縦左上
(True, WIDTH - SEG_WIDTH // 2, HEIGHT // 2 - SEG_HEIGHT // 2), # 縦右上
(False, WIDTH // 2, HEIGHT // 2), # 横中
(True, SEG_WIDTH // 2, HEIGHT // 2 + SEG_HEIGHT // 2), # 縦左下
(True, WIDTH - SEG_WIDTH // 2, HEIGHT // 2 + SEG_HEIGHT // 2), # 縦右下
(False, WIDTH // 2, HEIGHT - SEG_WIDTH // 2) # 横下
)
def __init__(self, master, color_seg_on, cnf={}, **kw):
super().__init__(master, cnf, **kw)
self.color_seg_on = color_seg_on
self._build()
def _build(self):
'''基準セグメント座標を回転・移動して7つの六角形セグメントを作成する'''
self.segs = []
for is_rotate, x_shift, y_shift in self.SEG_TRANSFORM:
if is_rotate:
# 回転必要な場合は、基準セグメントの頂点の座標を90度を回転
r_xs = [-n for n in self.SEG_YS]
r_ys = [n for n in self.SEG_XS]
else:
# 回転不要な場合は、基準セグメントの頂点の座標をそのまま使用
r_xs = self.SEG_XS
r_ys = self.SEG_YS
# 基準セグメントの各頂点を移動
t_xs = [n + x_shift for n in r_xs]
t_ys = [n + y_shift for n in r_ys]
# 移動後の座標に六角形を作成してリストに追加
seg = self.create_polygon(
*[xy for xys in zip(t_xs, t_ys) for xy in xys],
fill=COLOR_SEG_OFF,
width=0,
)
self.segs.append(seg)
def update(self, number):
'''numberの数字を7セグメントで表示する'''
for seg, seg_on in zip(self.segs, self.SEG_ON[number]):
color = self.color_seg_on if seg_on else COLOR_SEG_OFF
self.itemconfig(seg, fill=color)
class Timer:
JST = timezone(timedelta(hours=9))
@classmethod
def time(cls):
now = datetime.now(tz=cls.JST)
return now.hour, now.minute, now.second, now.microsecond
class Drawer:
'''時計を描画するクラス'''
CANVAS_CLASSES = (
NumberCanvas, # 時の10の位
NumberCanvas, # 時の1の位
ColonCanvas,
NumberCanvas, # 分の10の位
NumberCanvas, # 分の1の位
ColonCanvas,
NumberCanvas, # 秒の10の位
NumberCanvas, # 秒の1の位
)
def __init__(self, master, color_seg_on):
'''6個の数字キャンバスと2個のコロンキャンバスを作成'''
self.number_canvases = []
for canvas_class in self.CANVAS_CLASSES:
canvas = canvas_class(
master,
color_seg_on,
width=canvas_class.WIDTH,
height=canvas_class.HEIGHT,
bg=COLOR_BG,
highlightthickness=0,
)
if canvas_class == NumberCanvas:
self.number_canvases.append(canvas)
# 左から順番にpackで詰めていく
canvas.pack(side=tk.LEFT, padx=CANVAS_PAD, pady=CANVAS_PAD)
def update(self, hour, minute, second):
'''時分秒の各桁毎にNumberCanvasに表示する'''
numbers = (
*divmod(hour, 10),
*divmod(minute, 10),
*divmod(second, 10),
)
for canvas, number in zip(self.number_canvases, numbers):
canvas.update(number)
class DigitalClock:
def __init__(self, master, color_seg_on):
self.master = master
self.drawer = Drawer(master, color_seg_on)
def update(self):
'''表示時刻を更新する'''
hour, minute, second, microsecond = Timer.time()
self.drawer.update(hour, minute, second)
# 単に1000を指定すると処理遅延が蓄積して一度に2秒進むことがあるため、
# microsecondを使って次の秒までの残り時間を計算して指定する
self.master.after(1000 - microsecond // 1000, self.update)
if __name__ == "__main__":
app = tk.Tk()
app.title("Digital Clock")
app.config(bg=COLOR_BG)
clock = DigitalClock(app, color_seg_on="blue")
clock.update() # オブジェクト生成が終わってから更新処理開始
app.mainloop() # 描画・イベント処理ループ開始
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment