Weather: default to 5-day forecast, add weekday+date header line
- forecast_days now defaults to 5 (was 3) across default_settings(),
check()'s fallback, and the settings form's pre-selected option.
- New show_date setting (on by default): weekday + date in the same
"Samstag, 25. Juli" format Clock already used. Combined with the
city name into one header line above the icon/temp block when both
are on ("Münster · Samstag, 25. Juli"), each independently
toggleable.
- Moved the weekday/month name lists and the "Weekday, D. Month"
formatting out of clock.py into render_utils.format_date_de(), so
Weather reuses the exact same formatting instead of a second copy
that could drift.
This commit is contained in:
1 parent
78337a99d2
commit
7601f746fe
5 files changed
+50
-32
No files matched your search
+2
-8
@@ -5,16 +5,10 @@ import time
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from screens.base import Screen
|
||||
from screens.render_utils import load_font, text_width
|
||||
from screens.render_utils import format_date_de, load_font, text_width
|
||||
|
||||
LINE_GAP = 0.04 # fraction of min(width, height) between the time and date lines
|
||||
|
||||
WEEKDAYS_DE = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
|
||||
MONTHS_DE = [
|
||||
"Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||
"Juli", "August", "September", "Oktober", "November", "Dezember",
|
||||
]
|
||||
|
||||
|
||||
class ClockScreen(Screen):
|
||||
id = "clock"
|
||||
@@ -31,7 +25,7 @@ class ClockScreen(Screen):
|
||||
|
||||
now = time.localtime()
|
||||
time_str = state or time.strftime("%H:%M", now)
|
||||
date_str = f"{WEEKDAYS_DE[now.tm_wday]}, {now.tm_mday}. {MONTHS_DE[now.tm_mon - 1]}"
|
||||
date_str = format_date_de(now)
|
||||
|
||||
scale = min(width, height)
|
||||
time_font = load_font(size=int(scale * 0.32), bold=True)
|
||||
|
||||
@@ -6,6 +6,7 @@ dithering by default) -- so screens can draw with normal anti-aliased RGB
|
||||
text and full-color cover art without worrying about the palette themselves.
|
||||
"""
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PIL import Image, ImageFont
|
||||
@@ -14,6 +15,17 @@ FONTS_DIR = Path(__file__).resolve().parent.parent / "fonts"
|
||||
|
||||
_font_cache: dict[tuple[str, int], ImageFont.FreeTypeFont] = {}
|
||||
|
||||
WEEKDAYS_DE = ["Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"]
|
||||
MONTHS_DE = [
|
||||
"Januar", "Februar", "März", "April", "Mai", "Juni",
|
||||
"Juli", "August", "September", "Oktober", "November", "Dezember",
|
||||
]
|
||||
|
||||
|
||||
def format_date_de(t: time.struct_time | None = None) -> str:
|
||||
t = t or time.localtime()
|
||||
return f"{WEEKDAYS_DE[t.tm_wday]}, {t.tm_mday}. {MONTHS_DE[t.tm_mon - 1]}"
|
||||
|
||||
|
||||
def load_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
|
||||
key = ("bold" if bold else "regular", size)
|
||||
|
||||
+27
-20
@@ -8,7 +8,7 @@ import requests
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from screens.base import Screen
|
||||
from screens.render_utils import load_font, text_width, truncate_to_width
|
||||
from screens.render_utils import format_date_de, load_font, text_width, truncate_to_width
|
||||
from screens.weather_icons import WMO_CODES, draw_weather_icon
|
||||
|
||||
BLACK = (0, 0, 0)
|
||||
@@ -31,12 +31,13 @@ class WeatherScreen(Screen):
|
||||
|
||||
def default_settings(self) -> dict:
|
||||
return {
|
||||
"city": "", "latitude": None, "longitude": None, "forecast_days": 3, "update_minutes": 30,
|
||||
"city": "", "latitude": None, "longitude": None, "forecast_days": 5, "update_minutes": 30,
|
||||
"show_feels_like": True,
|
||||
"show_high_low": True,
|
||||
"show_rain_chance": True,
|
||||
"show_hourly_graph": True,
|
||||
"show_city": True,
|
||||
"show_date": True,
|
||||
}
|
||||
|
||||
def get_poll_interval(self, settings: dict) -> int:
|
||||
@@ -79,9 +80,9 @@ class WeatherScreen(Screen):
|
||||
logger.warning("Weather screen has no latitude/longitude configured")
|
||||
return None
|
||||
|
||||
forecast_days = settings.get("forecast_days", 3)
|
||||
forecast_days = settings.get("forecast_days", 5)
|
||||
if forecast_days not in FORECAST_DAY_CHOICES:
|
||||
forecast_days = 3
|
||||
forecast_days = 5
|
||||
|
||||
response = requests.get(
|
||||
"https://api.open-meteo.com/v1/forecast",
|
||||
@@ -214,26 +215,32 @@ class WeatherScreen(Screen):
|
||||
text_block_h = temp_h + text_gap + desc_h + ((text_gap + extra_h) if extra_line else 0)
|
||||
current_h = max(icon_size, text_block_h)
|
||||
|
||||
# City name, above the icon/temp block -- just the city itself, not
|
||||
# the full "City, State, Country" geocode label (too long/cluttered
|
||||
# for a dashboard header). Nothing to show for manually-entered
|
||||
# coordinates, since there's no resolved place name for those.
|
||||
city_font = load_font(size=int(scale * 0.045), bold=True)
|
||||
city_label = ""
|
||||
city_h = city_gap = city_top = 0
|
||||
# Header line above the icon/temp block: city name (just the city
|
||||
# itself, not the full "City, State, Country" geocode label -- too
|
||||
# cluttered for a dashboard) and/or today's weekday + date, same
|
||||
# format as the Clock screen. Nothing to show for city specifically
|
||||
# with manually-entered coordinates, since there's no resolved place
|
||||
# name for those.
|
||||
header_font = load_font(size=int(scale * 0.045), bold=True)
|
||||
header_parts = []
|
||||
if settings.get("show_city", True) and settings.get("city"):
|
||||
city_label = truncate_to_width(draw, settings["city"].split(",")[0].strip(), city_font, width - 2 * pad)
|
||||
if city_label:
|
||||
city_left, city_top, city_right, city_bottom = draw.textbbox((0, 0), city_label, font=city_font)
|
||||
city_h = city_bottom - city_top
|
||||
city_gap = height * 0.025
|
||||
header_parts.append(settings["city"].split(",")[0].strip())
|
||||
if settings.get("show_date", True):
|
||||
header_parts.append(format_date_de())
|
||||
header_label = truncate_to_width(draw, " · ".join(header_parts), header_font, width - 2 * pad) if header_parts else ""
|
||||
|
||||
total_top_h = city_h + city_gap + current_h
|
||||
header_h = header_gap = header_top = 0
|
||||
if header_label:
|
||||
header_left, header_top, header_right, header_bottom = draw.textbbox((0, 0), header_label, font=header_font)
|
||||
header_h = header_bottom - header_top
|
||||
header_gap = height * 0.025
|
||||
|
||||
total_top_h = header_h + header_gap + current_h
|
||||
top_pad = (height - total_top_h) / 2 if lower_sections == 0 else height * 0.05
|
||||
|
||||
if city_label:
|
||||
draw.text((pad, top_pad - city_top), city_label, font=city_font, fill="black")
|
||||
block_top = top_pad + city_h + city_gap
|
||||
if header_label:
|
||||
draw.text((pad, top_pad - header_top), header_label, font=header_font, fill="black")
|
||||
block_top = top_pad + header_h + header_gap
|
||||
|
||||
icon_cx = pad + icon_size / 2
|
||||
icon_cy = block_top + current_h / 2
|
||||
|
||||
Reference in new issue
Block a user