fucknugget
This commit is contained in:
292
display.py
292
display.py
@@ -6,21 +6,30 @@ Renders the JARVIS UI overlay via X11.
|
|||||||
|
|
||||||
Development: python display.py
|
Development: python display.py
|
||||||
Kiosk: DISPLAY=:0 python display.py --fullscreen
|
Kiosk: DISPLAY=:0 python display.py --fullscreen
|
||||||
|
|
||||||
|
Weather via Open-Meteo (free, no API key).
|
||||||
|
Location set by --location postcode (default BH8 8JZ).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
import math
|
import math
|
||||||
|
import threading
|
||||||
|
import time as _time
|
||||||
import argparse
|
import argparse
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
_parser = argparse.ArgumentParser(description='JARVIS display', add_help=True)
|
_parser = argparse.ArgumentParser(description='JARVIS display', add_help=True)
|
||||||
_parser.add_argument(
|
_parser.add_argument('--fullscreen', '-fs', action='store_true',
|
||||||
'--fullscreen', '-fs', action='store_true',
|
|
||||||
help='Run fullscreen under X11/Wayland')
|
help='Run fullscreen under X11/Wayland')
|
||||||
_parser.add_argument('--width', type=int, default=1280,
|
_parser.add_argument('--width', type=int, default=1280,
|
||||||
help='Screen width (default 1280)')
|
help='Screen width (default 1280)')
|
||||||
_parser.add_argument('--height', type=int, default=800,
|
_parser.add_argument('--height', type=int, default=800,
|
||||||
help='Screen height (default 800)')
|
help='Screen height (default 800)')
|
||||||
|
_parser.add_argument('--location', type=str, default='BH8 8JZ',
|
||||||
|
help='UK postcode for weather (default BH8 8JZ)')
|
||||||
_args, _ = _parser.parse_known_args()
|
_args, _ = _parser.parse_known_args()
|
||||||
|
|
||||||
import pygame
|
import pygame
|
||||||
@@ -30,142 +39,192 @@ BLACK = ( 0, 0, 0)
|
|||||||
WHITE = (255, 255, 255)
|
WHITE = (255, 255, 255)
|
||||||
GRAY = (160, 160, 160)
|
GRAY = (160, 160, 160)
|
||||||
DIM_GRAY = ( 80, 80, 80)
|
DIM_GRAY = ( 80, 80, 80)
|
||||||
WIRE_COL = ( 58, 58, 58) # subdued wireframe line colour
|
WIRE_COL = ( 58, 58, 58)
|
||||||
|
WIRE_EYE = ( 95, 95, 95)
|
||||||
|
|
||||||
|
# ── WMO weather codes (Open-Meteo) ────────────────────────────────────────────
|
||||||
|
_WMO = {
|
||||||
|
0: 'Clear sky', 1: 'Mainly clear', 2: 'Partly cloudy', 3: 'Overcast',
|
||||||
|
45: 'Fog', 48: 'Freezing fog',
|
||||||
|
51: 'Light drizzle', 53: 'Drizzle', 55: 'Heavy drizzle',
|
||||||
|
61: 'Light rain', 63: 'Rain', 65: 'Heavy rain',
|
||||||
|
71: 'Light snow', 73: 'Snow', 75: 'Heavy snow', 77: 'Snow grains',
|
||||||
|
80: 'Light showers', 81: 'Showers', 82: 'Heavy showers',
|
||||||
|
85: 'Snow showers', 86: 'Heavy snow showers',
|
||||||
|
95: 'Thunderstorm', 96: 'Thunderstorm + hail', 99: 'Thunderstorm + hail',
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Weather state (updated by background thread) ──────────────────────────────
|
||||||
|
_weather: dict = {} # keys: 'week' (list), 'hours' (list), 'location' (str)
|
||||||
|
_weather_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _postcode_to_latlon(postcode: str) -> tuple[float, float]:
|
||||||
|
url = 'https://api.postcodes.io/postcodes/' + urllib.parse.quote(postcode.replace(' ', ''))
|
||||||
|
with urllib.request.urlopen(url, timeout=10) as r:
|
||||||
|
data = json.loads(r.read())
|
||||||
|
return data['result']['latitude'], data['result']['longitude']
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_weather(lat: float, lon: float) -> None:
|
||||||
|
url = (
|
||||||
|
'https://api.open-meteo.com/v1/forecast'
|
||||||
|
f'?latitude={lat:.4f}&longitude={lon:.4f}'
|
||||||
|
'&daily=weather_code,temperature_2m_max,temperature_2m_min'
|
||||||
|
'&hourly=temperature_2m,weather_code'
|
||||||
|
'&timezone=Europe%2FLondon'
|
||||||
|
'&forecast_days=7'
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(url, timeout=10) as r:
|
||||||
|
data = json.loads(r.read())
|
||||||
|
|
||||||
|
daily = data['daily']
|
||||||
|
hourly = data['hourly']
|
||||||
|
|
||||||
|
week = []
|
||||||
|
for i, date_str in enumerate(daily['time']):
|
||||||
|
d = datetime.strptime(date_str, '%Y-%m-%d')
|
||||||
|
week.append({
|
||||||
|
'day': 'Today' if i == 0 else d.strftime('%A'),
|
||||||
|
'date': d.strftime('%-d %b'),
|
||||||
|
'high': round(daily['temperature_2m_max'][i]),
|
||||||
|
'low': round(daily['temperature_2m_min'][i]),
|
||||||
|
'desc': _WMO.get(daily['weather_code'][i], ''),
|
||||||
|
})
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
hours = []
|
||||||
|
for i, ts in enumerate(hourly['time']):
|
||||||
|
t = datetime.strptime(ts, '%Y-%m-%dT%H:%M')
|
||||||
|
if t < now or t.date() != now.date():
|
||||||
|
continue
|
||||||
|
hours.append({
|
||||||
|
'label': t.strftime('%-I%p').lower(),
|
||||||
|
'temp': round(hourly['temperature_2m'][i]),
|
||||||
|
})
|
||||||
|
|
||||||
|
with _weather_lock:
|
||||||
|
_weather['week'] = week
|
||||||
|
_weather['hours'] = hours
|
||||||
|
|
||||||
|
|
||||||
|
def _weather_worker(lat: float, lon: float) -> None:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
_fetch_weather(lat, lon)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_time.sleep(1800)
|
||||||
|
|
||||||
|
|
||||||
# ── Wireframe face ────────────────────────────────────────────────────────────
|
# ── Wireframe face ────────────────────────────────────────────────────────────
|
||||||
# Pure 2-D face drawing. Face shape is defined by an explicit width profile;
|
|
||||||
# eye sockets, nose bridge and chin are drawn as distinct features so the
|
|
||||||
# brain immediately reads "face" rather than a geometric solid.
|
|
||||||
|
|
||||||
WIRE_EYE = ( 95, 95, 95) # slightly brighter for eye/feature lines
|
|
||||||
|
|
||||||
def _hw(t: float, rx: float) -> float:
|
def _hw(t: float, rx: float) -> float:
|
||||||
"""
|
|
||||||
Face half-width in pixels at normalised height t.
|
|
||||||
t = -1 → crown (top)
|
|
||||||
t = 0 → cheek level (widest, ~= rx)
|
|
||||||
t = +1 → chin tip
|
|
||||||
Upper half: rounded ellipse.
|
|
||||||
Lower half: faster taper toward chin.
|
|
||||||
"""
|
|
||||||
if t <= 0.0:
|
if t <= 0.0:
|
||||||
return rx * math.sqrt(max(0.0, 1.0 - t * t))
|
return rx * math.sqrt(max(0.0, 1.0 - t * t))
|
||||||
else:
|
|
||||||
return rx * math.sqrt(max(0.0, 1.0 - t * t)) * (1.0 - 0.30 * t)
|
return rx * math.sqrt(max(0.0, 1.0 - t * t)) * (1.0 - 0.30 * t)
|
||||||
|
|
||||||
|
|
||||||
def draw_wireframe_face(surface: pygame.Surface,
|
def draw_wireframe_face(surface: pygame.Surface,
|
||||||
cx: int, cy: int,
|
cx: int, cy: int, rx: float, ry: float) -> None:
|
||||||
rx: float, ry: float) -> None:
|
N_H, N_V, PTS = 13, 11, 40
|
||||||
"""
|
|
||||||
Draw a 2-D face wireframe centred at (cx, cy).
|
|
||||||
Coordinate system: t ∈ [−1, +1] (−1=crown, 0=cheeks, +1=chin)
|
|
||||||
x_fraction ∈ [−1, +1] (fraction of half-width at t)
|
|
||||||
"""
|
|
||||||
N_H = 13 # horizontal grid lines
|
|
||||||
N_V = 11 # vertical grid lines (including edges)
|
|
||||||
PTS = 40 # interpolation steps per curve
|
|
||||||
|
|
||||||
# Helpers ─────────────────────────────────────────────────────────────────
|
def sc(xf, t):
|
||||||
def sc(xf: float, t: float) -> tuple[int, int]:
|
|
||||||
"""xf (fraction of half-width) + height t → screen pixel."""
|
|
||||||
w = _hw(t, rx)
|
w = _hw(t, rx)
|
||||||
# Slight forward bow: face curves toward viewer at cheek level
|
|
||||||
bow = int(0.06 * w * (1.0 - t*t) * (1.0 - xf*xf))
|
bow = int(0.06 * w * (1.0 - t*t) * (1.0 - xf*xf))
|
||||||
return (int(cx + xf*w), int(cy + t*ry) - bow)
|
return (int(cx + xf*w), int(cy + t*ry) - bow)
|
||||||
|
|
||||||
def draw(pts, close=False, col=WIRE_COL):
|
def draw(pts, col=WIRE_COL):
|
||||||
if len(pts) >= 2:
|
if len(pts) >= 2:
|
||||||
pygame.draw.lines(surface, col, close, pts, 1)
|
pygame.draw.lines(surface, col, False, pts, 1)
|
||||||
|
|
||||||
# Face outline (left and right silhouette) ────────────────────────────────
|
t_vals = [-1.0 + 2.0*i/(PTS-1) for i in range(PTS)]
|
||||||
t_vals = [(-1.0 + 2.0 * i / (PTS - 1)) for i in range(PTS)]
|
draw([sc(-1.0, t) for t in t_vals])
|
||||||
draw([sc(-1.0, t) for t in t_vals]) # left edge
|
draw([sc( 1.0, t) for t in t_vals])
|
||||||
draw([sc( 1.0, t) for t in t_vals]) # right edge
|
pygame.draw.lines(surface, WIRE_COL, False, [sc(-1.0,-1.0), sc(1.0,-1.0)], 1)
|
||||||
# Crown arc (straight line across the very top)
|
pygame.draw.lines(surface, WIRE_COL, False, [sc(-0.6,1.0), sc(0.0,1.02), sc(0.6,1.0)], 1)
|
||||||
draw([sc(-1.0, -1.0), sc(1.0, -1.0)])
|
|
||||||
# Chin arc
|
|
||||||
draw([sc(-0.6, 1.0), sc(0.0, 1.02), sc(0.6, 1.0)])
|
|
||||||
|
|
||||||
# Horizontal grid lines ───────────────────────────────────────────────────
|
|
||||||
for i in range(1, N_H):
|
for i in range(1, N_H):
|
||||||
t = -1.0 + 2.0*i/N_H
|
t = -1.0 + 2.0*i/N_H
|
||||||
draw([sc(-1.0 + 2.0*j/(PTS-1), t) for j in range(PTS)])
|
draw([sc(-1.0 + 2.0*j/(PTS-1), t) for j in range(PTS)])
|
||||||
|
|
||||||
# Vertical grid lines ─────────────────────────────────────────────────────
|
|
||||||
for i in range(1, N_V):
|
for i in range(1, N_V):
|
||||||
xf = -1.0 + 2.0*i/N_V
|
xf = -1.0 + 2.0*i/N_V
|
||||||
draw([sc(xf, t) for t in t_vals])
|
draw([sc(xf, t) for t in t_vals])
|
||||||
|
|
||||||
# ── Eye sockets ───────────────────────────────────────────────────────────
|
eye_t, eye_xf = -0.38, 0.35
|
||||||
# Each eye is a small ellipse drawn in a slightly brighter colour so it
|
eye_w = _hw(eye_t, rx)
|
||||||
# immediately reads as a facial feature.
|
|
||||||
eye_t = -0.38 # height: upper face (~35% from crown)
|
|
||||||
eye_xf = 0.35 # lateral offset (fraction of half-width at eye_t)
|
|
||||||
eye_w = _hw(eye_t, rx) # face half-width at eye level
|
|
||||||
e_rx = int(eye_w * 0.24)
|
e_rx = int(eye_w * 0.24)
|
||||||
e_ry = int(ry * 0.09)
|
e_ry = int(ry * 0.09)
|
||||||
|
|
||||||
for sign in (-1, +1):
|
for sign in (-1, +1):
|
||||||
ecx = int(cx + sign * eye_xf * eye_w)
|
ecx = int(cx + sign * eye_xf * eye_w)
|
||||||
ecy = int(cy + eye_t * ry)
|
ecy = int(cy + eye_t * ry)
|
||||||
eye_pts = [
|
pts = [(int(ecx + e_rx*math.cos(2*math.pi*k/28)),
|
||||||
(int(ecx + e_rx * math.cos(2 * math.pi * k / 28)),
|
int(ecy + e_ry*math.sin(2*math.pi*k/28))) for k in range(29)]
|
||||||
int(ecy + e_ry * math.sin(2 * math.pi * k / 28)))
|
pygame.draw.lines(surface, WIRE_EYE, True, pts, 1)
|
||||||
for k in range(28)
|
|
||||||
]
|
|
||||||
draw(eye_pts + [eye_pts[0]], col=WIRE_EYE)
|
|
||||||
|
|
||||||
# ── Nose bridge ───────────────────────────────────────────────────────────
|
|
||||||
# Two short lines running from inner eye corners down to nose tip
|
|
||||||
inner = int(eye_w * 0.12)
|
inner = int(eye_w * 0.12)
|
||||||
e_btm = int(cy + eye_t*ry) + e_ry
|
e_btm = int(cy + eye_t*ry) + e_ry
|
||||||
nose_y = int(cy + 0.08*ry)
|
nose_y = int(cy + 0.08*ry)
|
||||||
nose_w = int(eye_w * 0.07)
|
nose_w = int(eye_w * 0.07)
|
||||||
draw([(cx - inner, e_btm), (cx - nose_w, nose_y)], col=WIRE_COL)
|
draw([(cx-inner, e_btm), (cx-nose_w, nose_y)])
|
||||||
draw([(cx + inner, e_btm), (cx + nose_w, nose_y)], col=WIRE_COL)
|
draw([(cx+inner, e_btm), (cx+nose_w, nose_y)])
|
||||||
|
|
||||||
|
|
||||||
# ── Clock / date ─────────────────────────────────────────────────────────────
|
# ── Clock ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def draw_clock(surface: pygame.Surface, fonts: dict,
|
def draw_clock(surface: pygame.Surface, fonts: dict, x: int, y: int) -> None:
|
||||||
x: int, y: int) -> None:
|
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
date_str = now.strftime('%A, %B %-d, %Y')
|
t_surf = fonts['large'].render(now.strftime('%-H:%M'), True, WHITE)
|
||||||
time_str = now.strftime('%-H:%M')
|
s_surf = fonts['small'].render(now.strftime('%S'), True, DIM_GRAY)
|
||||||
secs_str = now.strftime('%S')
|
surface.blit(fonts['small'].render(now.strftime('%A, %-d %B %Y'), True, GRAY), (x, y))
|
||||||
|
|
||||||
# Date row
|
|
||||||
surface.blit(fonts['small'].render(date_str, True, GRAY), (x, y))
|
|
||||||
|
|
||||||
# Large time + superscript seconds
|
|
||||||
t_surf = fonts['large'].render(time_str, True, WHITE)
|
|
||||||
s_surf = fonts['small'].render(secs_str, True, DIM_GRAY)
|
|
||||||
ty = y + fonts['small'].get_height() + 4
|
ty = y + fonts['small'].get_height() + 4
|
||||||
surface.blit(t_surf, (x, ty))
|
surface.blit(t_surf, (x, ty))
|
||||||
surface.blit(s_surf, (x + t_surf.get_width() + 2,
|
surface.blit(s_surf, (x + t_surf.get_width() + 2,
|
||||||
ty + t_surf.get_height() - s_surf.get_height() - 8))
|
ty + t_surf.get_height() - s_surf.get_height() - 8))
|
||||||
|
|
||||||
|
|
||||||
# ── Status line ───────────────────────────────────────────────────────────────
|
# ── Weather panel ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def draw_status(surface: pygame.Surface, fonts: dict,
|
def draw_weather(surface: pygame.Surface, fonts: dict, x: int, y: int) -> None:
|
||||||
text: str, cx: int, y: int) -> None:
|
with _weather_lock:
|
||||||
surf = fonts['small'].render(text, True, GRAY)
|
week = list(_weather.get('week', []))
|
||||||
surface.blit(surf, (cx - surf.get_width() // 2, y))
|
hours = list(_weather.get('hours', []))
|
||||||
|
|
||||||
|
sh = fonts['small'].get_height()
|
||||||
|
lh = fonts['large'].get_height()
|
||||||
|
|
||||||
|
if not week:
|
||||||
|
surface.blit(fonts['small'].render('Weather loading…', True, DIM_GRAY), (x, y))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Rotate through the 7 days every 4 seconds
|
||||||
|
day = week[int(_time.time() / 4) % len(week)]
|
||||||
|
|
||||||
|
surface.blit(fonts['small'].render(f"{day['day']} {day['date']}", True, GRAY), (x, y))
|
||||||
|
t_surf = fonts['large'].render(f"{day['high']}° / {day['low']}°", True, WHITE)
|
||||||
|
ty = y + sh + 4
|
||||||
|
surface.blit(t_surf, (x, ty))
|
||||||
|
surface.blit(fonts['small'].render(day['desc'], True, GRAY), (x, ty + lh + 3))
|
||||||
|
|
||||||
|
# Hourly strip for today — next 8 hours, displayed as "2pm 14° 3pm 13° …"
|
||||||
|
if hours:
|
||||||
|
strip_y = ty + lh + 3 + sh + 8
|
||||||
|
col_w = 52
|
||||||
|
for i, h in enumerate(hours[:8]):
|
||||||
|
hx = x + i * col_w
|
||||||
|
surface.blit(fonts['small'].render(h['label'], True, DIM_GRAY), (hx, strip_y))
|
||||||
|
surface.blit(fonts['small'].render(f"{h['temp']}°", True, GRAY),
|
||||||
|
(hx, strip_y + sh + 1))
|
||||||
|
|
||||||
|
|
||||||
def draw_dot(surface: pygame.Surface, cx: int, y: int,
|
def draw_dot(surface: pygame.Surface, cx: int, y: int, radius: int = 9) -> None:
|
||||||
radius: int = 9) -> None:
|
|
||||||
pygame.draw.circle(surface, WHITE, (cx, y), radius)
|
pygame.draw.circle(surface, WHITE, (cx, y), radius)
|
||||||
|
|
||||||
|
|
||||||
# ── Font helper ───────────────────────────────────────────────────────────────
|
# ── Font helper ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _load_font(size: int, bold: bool = False) -> pygame.font.Font:
|
def _load_font(size: int, bold: bool = False) -> pygame.font.Font:
|
||||||
"""Try a list of clean system fonts, fall back to pygame built-in."""
|
for name in ('DejaVuSans', 'FreeSans', 'LiberationSans', 'Helvetica', 'Arial', None):
|
||||||
for name in ('DejaVuSans', 'FreeSans', 'LiberationSans',
|
|
||||||
'Helvetica', 'Arial', None):
|
|
||||||
try:
|
try:
|
||||||
f = pygame.font.SysFont(name, size, bold=bold)
|
f = pygame.font.SysFont(name, size, bold=bold)
|
||||||
if f:
|
if f:
|
||||||
@@ -195,59 +254,56 @@ def main() -> None:
|
|||||||
'small': _load_font(13),
|
'small': _load_font(13),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Layout anchors — all relative to screen size so they adapt to resolution
|
face_rx = int(min(W, H) * 0.085)
|
||||||
face_rx = int(min(W, H) * 0.085) # half-width at cheek level
|
face_ry = int(min(W, H) * 0.100)
|
||||||
face_ry = int(min(W, H) * 0.100) # half-height crown→chin
|
|
||||||
face_cx = W // 2
|
face_cx = W // 2
|
||||||
face_cy = int(face_ry * 1.2) + 8 # nearly touches the top edge
|
face_cy = int(face_ry * 1.2) + 8
|
||||||
status_y = int(H * 0.82)
|
|
||||||
dot_y = int(H * 0.895)
|
dot_y = int(H * 0.895)
|
||||||
|
|
||||||
# ── Load face wireframe PNG if present ────────────────────────────────────
|
clock_block_h = fonts['small'].get_height() + 4 + fonts['large'].get_height()
|
||||||
# Place any face wireframe PNG at assets/face_wire.png and it will be
|
clock_y = face_cy - clock_block_h // 2
|
||||||
# used automatically. Black background is composited away via BLEND_ADD
|
weather_x = face_cx + face_rx + 30
|
||||||
# so only the bright lines show. Falls back to parametric drawing.
|
|
||||||
|
# Resolve postcode and start weather thread
|
||||||
|
try:
|
||||||
|
lat, lon = _postcode_to_latlon(_args.location)
|
||||||
|
threading.Thread(target=_weather_worker, args=(lat, lon), daemon=True).start()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
_here = os.path.dirname(os.path.abspath(__file__))
|
_here = os.path.dirname(os.path.abspath(__file__))
|
||||||
_png_path = os.path.join(_here, 'assets', 'face_wire.png')
|
png_path = os.path.join(_here, 'assets', 'face_wire.png')
|
||||||
face_img = None
|
face_img = None
|
||||||
if os.path.exists(_png_path):
|
if os.path.exists(png_path):
|
||||||
raw = pygame.image.load(_png_path).convert()
|
raw = pygame.image.load(png_path).convert()
|
||||||
img_src_w, img_src_h = raw.get_size()
|
iw, ih = raw.get_size()
|
||||||
# Scale to fit face slot while preserving aspect ratio
|
scale = min((face_rx * 2.2) / iw, (face_ry * 2.4) / ih)
|
||||||
scale = min((face_rx * 2.2) / img_src_w,
|
face_img = pygame.transform.smoothscale(
|
||||||
(face_ry * 2.4) / img_src_h)
|
raw, (max(1, int(iw*scale)), max(1, int(ih*scale))))
|
||||||
img_w = max(1, int(img_src_w * scale))
|
|
||||||
img_h = max(1, int(img_src_h * scale))
|
|
||||||
face_img = pygame.transform.smoothscale(raw, (img_w, img_h))
|
|
||||||
|
|
||||||
status_text = 'Initializing...'
|
pg_clock = pygame.time.Clock()
|
||||||
|
|
||||||
clock = pygame.time.Clock()
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.QUIT:
|
if event.type == pygame.QUIT:
|
||||||
pygame.quit()
|
pygame.quit(); return
|
||||||
return
|
if event.type == pygame.KEYDOWN and event.key in (pygame.K_ESCAPE, pygame.K_q):
|
||||||
if event.type == pygame.KEYDOWN:
|
pygame.quit(); return
|
||||||
if event.key in (pygame.K_ESCAPE, pygame.K_q):
|
|
||||||
pygame.quit()
|
|
||||||
return
|
|
||||||
|
|
||||||
screen.fill(BLACK)
|
screen.fill(BLACK)
|
||||||
|
|
||||||
if face_img:
|
if face_img:
|
||||||
screen.blit(face_img,
|
screen.blit(face_img, face_img.get_rect(center=(face_cx, face_cy)),
|
||||||
face_img.get_rect(center=(face_cx, face_cy)),
|
|
||||||
special_flags=pygame.BLEND_ADD)
|
special_flags=pygame.BLEND_ADD)
|
||||||
else:
|
else:
|
||||||
draw_wireframe_face(screen, face_cx, face_cy, face_rx, face_ry)
|
draw_wireframe_face(screen, face_cx, face_cy, face_rx, face_ry)
|
||||||
clock_block_h = fonts['small'].get_height() + 4 + fonts['large'].get_height()
|
|
||||||
draw_clock(screen, fonts, 20, face_cy - clock_block_h // 2)
|
draw_clock(screen, fonts, 20, clock_y)
|
||||||
draw_status(screen, fonts, status_text, W // 2, status_y)
|
draw_weather(screen, fonts, weather_x, clock_y)
|
||||||
draw_dot(screen, W // 2, dot_y)
|
draw_dot(screen, W // 2, dot_y)
|
||||||
|
|
||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
clock.tick(10) # 10 fps is plenty for a status screen
|
pg_clock.tick(10)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user