| 1838 | r = 3 + i * 3 |
| 1839 | draw.arc((cx - r, cy - r, cx + r, cy + r), start=225, end=315, fill=0, width=1) |
| 1840 | left_used = 14 |
| 1841 | try: |
| 1842 | ip_octet = self.get_wifi_ip_last_octet() |
| 1843 | except Exception: |
| 1844 | ip_octet = None |
| 1845 | if ip_octet: |
| 1846 | draw.text((15, 4), ip_octet, font=font, fill=0) |
| 1847 | left_used = 15 + int(font.getlength(ip_octet)) |
| 1848 | except Exception: |
| 1849 | pass |
| 1850 | # Battery %, top-right (PiSugar) |
| 1851 | bat_w = 0 |
| 1852 | try: |
| 1853 | _ri = getattr(sd, 'ragnar_instance', None) |
| 1854 | _ps = getattr(_ri, 'pisugar_listener', None) if _ri else None |
| 1855 | if _ps and _ps.available: |
| 1856 | bl = _ps.get_battery_level() |
| 1857 | if bl is not None: |
| 1858 | bt = f"{int(round(bl))}%{'+' if _ps.is_charging() else ''}" |
| 1859 | bat_w = int(font.getlength(bt)) + 3 |
| 1860 | draw.text((W - bat_w + 1, 3), bt, font=font, fill=0) |
| 1861 | except Exception: |
| 1862 | pass |
| 1863 | # Centre the title in the strip between the glyph/IP and the battery. |
| 1864 | left_pad = max(18, left_used + 2) |
| 1865 | right_pad = max(18, bat_w) |
| 1866 | avail = W - left_pad - right_pad |
| 1867 | _title = self._unit_display_title() |
| 1868 | title_font = self._fit_font('Viking.TTF', sd.font_viking, _title, avail) |
| 1869 | tw = title_font.getlength(_title) |
| 1870 | draw.text((left_pad + (avail - tw) / 2, 1), _title, font=title_font, fill=0) |
| 1871 | draw.line((1, 16, W - 1, 16), fill=0) |
| 1872 | |
| 1873 | # --- two icon+count stat rows (icons ~30% smaller so they fit) --- |
| 1874 | icon_h = 13 # 18px source icons scaled down ~30% |
| 1875 | |
| 1876 | def _fit_icon(icon): |
| 1877 | if icon is None: |
| 1878 | return None |
| 1879 | try: |
| 1880 | if icon.height > icon_h: |
| 1881 | ratio = icon_h / icon.height |
| 1882 | return icon.resize((max(1, int(icon.width * ratio)), icon_h), Image.NEAREST) |
| 1883 | except Exception: |
| 1884 | return icon |
| 1885 | return icon |
| 1886 | |
| 1887 | def _row(y, items): |
| 1888 | slot = W // len(items) |
| 1889 | for i, (icon, val) in enumerate(items): |
| 1890 | x = i * slot + 2 |
| 1891 | icon = _fit_icon(icon) |
| 1892 | if icon is not None: |
| 1893 | try: |
| 1894 | image.paste(icon, (x, y)) |
| 1895 | tx = x + icon.width + 1 |
| 1896 | except Exception: |
| 1897 | tx = x + icon_h + 1 |