From 4dab3c8015ff5c1ed2219a86a636fd25a6727021 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Mon, 3 Nov 2025 10:06:20 -0500 Subject: [PATCH] =?UTF-8?q?v0.1.0=20=E2=80=94=20initial=20Telefact=20broad?= =?UTF-8?q?caster=20base=20(frame=20+=20renderer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 32 ++++++ src/__init__.py | 0 src/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 151 bytes .../telefact_renderer.cpython-313.pyc | Bin 0 -> 5747 bytes src/core/__init__.py | 0 src/core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 156 bytes .../telefact_frame.cpython-313.pyc | Bin 0 -> 3287 bytes src/core/telefact_body.py | 0 src/core/telefact_footer.py | 0 src/core/telefact_formatter.py | 0 src/core/telefact_frame.py | 42 ++++++++ src/core/telefact_header.py | 0 src/telefact_renderer.py | 97 ++++++++++++++++++ 13 files changed, 171 insertions(+) create mode 100644 main.py create mode 100644 src/__init__.py create mode 100644 src/__pycache__/__init__.cpython-313.pyc create mode 100644 src/__pycache__/telefact_renderer.cpython-313.pyc create mode 100644 src/core/__init__.py create mode 100644 src/core/__pycache__/__init__.cpython-313.pyc create mode 100644 src/core/__pycache__/telefact_frame.cpython-313.pyc create mode 100644 src/core/telefact_body.py create mode 100644 src/core/telefact_footer.py create mode 100644 src/core/telefact_formatter.py create mode 100644 src/core/telefact_frame.py create mode 100644 src/core/telefact_header.py create mode 100644 src/telefact_renderer.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..0d179e0 --- /dev/null +++ b/main.py @@ -0,0 +1,32 @@ +""" +Telefact — minimal runner +- Opens a 4:3 800×600 window +- Renders header (blue band) and footer (red band) +- No formatter yet; that’s next. +""" + +import tkinter as tk +from src.telefact_renderer import TelefactRenderer +from src.core.telefact_frame import TelefactFrame + +def main(): + root = tk.Tk() + root.title("Telefact — Broadcaster Prototype") + + renderer = TelefactRenderer(root, width=800, height=600, show_grid=False) + frame = TelefactFrame() + + # put a couple test glyphs so you can confirm alignment quickly + for i, ch in enumerate("TELEFACT"): + frame.set_cell(2 + i, 2, ch, "yellow") + for i, ch in enumerate("BROADCASTER BASE"): + frame.set_cell(2 + i, 4, ch, "white") + + renderer.render(frame) + + root.bind("", lambda e: root.quit()) + root.bind("q", lambda e: root.quit()) + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/__pycache__/__init__.cpython-313.pyc b/src/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f78681185faa1680d7448987f392416f5f6a030a GIT binary patch literal 151 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 literal 0 HcmV?d00001 diff --git a/src/core/__pycache__/telefact_frame.cpython-313.pyc b/src/core/__pycache__/telefact_frame.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..558292a4b41c8fda32ee227412b3504b412d392a GIT binary patch literal 3287 zcmb6bOKcNY@a@Nmf59ZS(*y`Ct#{k)bZBMsfkQ0{$!eDkOJbMY~(_6u6ZR4D~8M}ClppB<}1N2lUKBiZt_&SqAOg%VnoQq_0lH{ zT7Pye#?l^Vg$&T_Au&B9QwTbNB*3burH7m@a-98y0M$w1G7+g2CCkEeE8wkA3ZS(p zbKd9dTV4OIf^9+a8K<*RqO(DAL?k53l}S2EO7Nz+rmCRvjaOxKlxw4sWX6m%1cqNZ;^O+anQmr%KZ}PgD=`Yd2$T|i|fMIq<2eeCSR5u2rC#iN(RSZ3POv92sefZ>w zs8Ki)QJpGLMWbqwBKPAViCi8s-Ljm{rVUv(Ys< z`B`_}69?Bf+^)Y>Kc1Xx*`06MeLpnU5zoV~C7!Q8I?P{R^P++rWlk`^8-EV{F*Ft6hBd^QrB2_Kk_#3rh5Wb={OZ1_Mv-kT4d8|J_AyqXRlcr_h5 zr`LfQ4;mT{uIC@@^B=7BKCGyO`NLWlK$C+KH#rC`6#GDenl4ytRKe&w18df*_8(vZ zO@--Ubap?xZyzCvwN}kKJ4^7s8sNsDO+m&}D8sIhS*7|dnMOyZhifb7OMnMGoW!6} zJ$yU+hEu{?e5Mt4r3!zOZ1+4WndFvhfr8CMbkS-+v=+g82rx>t9)ON&uuudVgqi8q zRYO+POlD=S9eN0bjsbvN`}`x^Bj2WxsA=zn@#E0s(Br1PKc9Kh z*gC;{w`bz;)RrB?U0=tq#YdED-MO99{!I%Ui5+C7n)XilHZ6fZQr2{eX&3>{|4Ku& z5K?Hb@0^CPBlF+E##l9x6bpx#;+;dWvC{?sBDW&KIy$f%)zllJ>d-MCdKZ9|QHAh6 z3p6aaNOMa`Y$3=<x<#G_ zfM@kWyNXK-xLCF(Lrq3mS-dVKqr#~UmUk(Oh)L2v0&tZ)t*#w0#)d|R9#?N3?tB_v zmutV>ajWBTxOs$udL7?7!jE}IJ-N-J{@ke>fhCUAwmj!Z%@#YZ#aMtcjD;1~2uw=j zir3$2SDy1KX(Dtzh={E1tsmx-j8#H9>MtC|D(5b+9v|lVa=*%$jUc5%8KZxJ(8#GA zKUde5uWOqLh*NIyb<2wt(FZc6malI28>y>Q176i7em27rj)#?1^pmbZ0GB2yb=7BI zZnmTK<&KuHT2QPBfbHg)xo~qn+&pf~weHEc?z!LeOXR0WzV*mVAU@@ezwR|!Yvt>m zylyNv?3Q|s=YUorSm||@bHA^e;4&t>TZp~|K*95|-qGIi(A?(jdH995-)XWwm*&siCP&zgStg_9!1%h}&dTHUs zI@Yx_cW8kC8dofICupJjumgw-ZqBt4Hvk$}ULacZa;|N;MkH;UIP?qEf*=6X{jnXT7(D<>Ji$6fVu52AW|8F?{vUrM L6MIeoSW^E2%*2Sm literal 0 HcmV?d00001 diff --git a/src/core/telefact_body.py b/src/core/telefact_body.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/telefact_footer.py b/src/core/telefact_footer.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/telefact_formatter.py b/src/core/telefact_formatter.py new file mode 100644 index 0000000..e69de29 diff --git a/src/core/telefact_frame.py b/src/core/telefact_frame.py new file mode 100644 index 0000000..7821f15 --- /dev/null +++ b/src/core/telefact_frame.py @@ -0,0 +1,42 @@ +""" +Telefact Frame (model) +- Defines a 40×24 grid and logical regions. +- No rendering here—pure data model. +""" + +class TelefactFrame: + def __init__(self, cols: int = 40, rows: int = 24): + self.cols = cols + self.rows = rows + + self.header_rows = 1 + self.footer_rows = 1 + self.body_rows = self.rows - (self.header_rows + self.footer_rows) + + # grid[row][col] -> (char, color_name) + self.grid = [[(" ", "white") for _ in range(self.cols)] for _ in range(self.rows)] + + # basic cell ops + def set_cell(self, col: int, row: int, char: str, color: str = "white"): + if 0 <= col < self.cols and 0 <= row < self.rows and char: + self.grid[row][col] = (char[0], color) + + def get_cell(self, col: int, row: int): + if 0 <= col < self.cols and 0 <= row < self.rows: + return self.grid[row][col] + return (" ", "white") + + def clear(self, char: str = " ", color: str = "white"): + for r in range(self.rows): + for c in range(self.cols): + self.grid[r][c] = (char, color) + + # regions + def header_region(self): + return range(0, self.header_rows) + + def body_region(self): + return range(self.header_rows, self.header_rows + self.body_rows) + + def footer_region(self): + return range(self.rows - self.footer_rows, self.rows) diff --git a/src/core/telefact_header.py b/src/core/telefact_header.py new file mode 100644 index 0000000..e69de29 diff --git a/src/telefact_renderer.py b/src/telefact_renderer.py new file mode 100644 index 0000000..54ff0f5 --- /dev/null +++ b/src/telefact_renderer.py @@ -0,0 +1,97 @@ +""" +Telefact Renderer (Tkinter) +- Renders a TelefactFrame to a Canvas. +- Pure Python (stdlib only). +""" + +import tkinter as tk +from typing import Dict +from src.core.telefact_frame import TelefactFrame + +PALETTE: Dict[str, str] = { + "black": "#000000", + "blue": "#0000FF", + "red": "#FF0000", + "magenta":"#FF00FF", + "green": "#00FF00", + "cyan": "#00FFFF", + "yellow": "#FFFF00", + "white": "#FFFFFF", +} + +class TelefactRenderer: + def __init__(self, root: tk.Tk, width: int = 800, height: int = 600, show_grid: bool = False): + self.root = root + self.width = width + self.height = height + self.show_grid = show_grid + + # 40×24 grid for Telefact pages + self.cols, self.rows = 40, 24 + self.cell_w = max(1, width // self.cols) + self.cell_h = max(1, height // self.rows) + self.w = self.cell_w * self.cols + self.h = self.cell_h * self.rows + + self.canvas = tk.Canvas( + root, width=self.w, height=self.h, + highlightthickness=0, bg=PALETTE["black"] + ) + self.canvas.pack() + + # simple monospace font; will swap for Teletext font later + self.font = ("Courier New", max(12, self.cell_h - 9), "bold") + + # ----- low-level drawing helpers ----- + def _gx(self, col: int) -> int: + return col * self.cell_w + + def _gy(self, row: int) -> int: + return row * self.cell_h + + def _fill_row(self, row: int, color: str) -> None: + self.canvas.create_rectangle( + self._gx(0), self._gy(row), self._gx(self.cols), self._gy(row + 1), + fill=PALETTE.get(color, color), width=0 + ) + + def _draw_char(self, col: int, row: int, char: str, color: str) -> None: + x = self._gx(col) + 2 + y = self._gy(row) + self.cell_h // 2 + self.canvas.create_text( + x, y, anchor="w", text=char, + font=self.font, fill=PALETTE.get(color, color) + ) + + def _draw_grid(self) -> None: + for c in range(self.cols + 1): + x = self._gx(c) + self.canvas.create_line(x, 0, x, self.h, fill="#202020") + for r in range(self.rows + 1): + y = self._gy(r) + self.canvas.create_line(0, y, self.w, y, fill="#202020") + + # ----- public API ----- + def render(self, frame: TelefactFrame) -> None: + """Paint the whole frame to the canvas.""" + self.canvas.delete("all") + + # background + self.canvas.create_rectangle(0, 0, self.w, self.h, + fill=PALETTE["black"], width=0) + + # header/footer colour bands + for r in frame.header_region(): + self._fill_row(r, "blue") + for r in frame.footer_region(): + self._fill_row(r, "red") + + # draw text cells + for row in range(frame.rows): + for col in range(frame.cols): + ch, fg = frame.grid[row][col] + if ch.strip(): + self._draw_char(col, row, ch, fg) + + if self.show_grid: + self._draw_grid()