v0.1.2 — Header finalized with background color and live clock
This commit is contained in:
parent
fa33334823
commit
76ade4bf0b
|
|
@ -1,16 +1,30 @@
|
|||
{
|
||||
"Mode": "Broadcaster",
|
||||
|
||||
"ScreenWidth": 800,
|
||||
"ScreenHeight": 600,
|
||||
"ShowGrid": false,
|
||||
|
||||
"Font": {
|
||||
"Name": "Modeseven",
|
||||
"Path": "assets/fonts/Modeseven.ttf",
|
||||
"Size": 18
|
||||
},
|
||||
"ShowGrid": false,
|
||||
|
||||
"Colours": {
|
||||
"Header": "blue",
|
||||
"Footer": "red",
|
||||
"Background": "black"
|
||||
"Background": "black",
|
||||
"TextPrimary": "white",
|
||||
"TextAccent": "yellow",
|
||||
"Clock": "yellow"
|
||||
},
|
||||
|
||||
"Header": {
|
||||
"SelectedPage": "P100",
|
||||
"CurrentPage": "100",
|
||||
"ServiceName": "Telefact",
|
||||
"ServiceFG": "yellow",
|
||||
"ServiceBG": "red"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
docs/CHANGELOG.md
Normal file
61
docs/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
# Telefact — Changelog
|
||||
|
||||
Project: Telefact
|
||||
Repository: Leak Technologies
|
||||
Language: Python (Tkinter Implementation)
|
||||
Version Line: v0.1.x (Foundational Builds)
|
||||
Grid Layout: 40×24 Teletext-Compatible
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
## [v0.1.2] — Header Finalization & Renderer Upgrade (2025-11-03)
|
||||
|
||||
### Added
|
||||
- Full background rendering support in TelefactRenderer
|
||||
- Each cell now supports independent background colour
|
||||
- Renderer draws proper CRT-style safe-area margins
|
||||
- TelefactHeader finalized
|
||||
- Centered "Telefact" block with blue background
|
||||
- White page numbers (P100 / 100)
|
||||
- Yellow live-updating clock (%b%d %H:%M:%S)
|
||||
- Accurate Ceefax-style alignment maintained across full grid width
|
||||
- Config.json updated with Header section for colour and text configuration
|
||||
- Updated TelefactFrame to support (char, fg, bg) tuples
|
||||
|
||||
### Fixed
|
||||
- Misaligned header text and spacing between elements
|
||||
- Incorrect handling of colour blocks for service provider
|
||||
- Missing current_page display now restored
|
||||
|
||||
### Structure
|
||||
telefact/
|
||||
├── src/
|
||||
│ ├── core/
|
||||
│ │ ├── telefact_frame.py
|
||||
│ │ ├── telefact_header.py
|
||||
│ │ └── telefact_formatter.py
|
||||
│ ├── telefact_renderer.py
|
||||
│ └── config_manager.py
|
||||
├── docs/
|
||||
│ ├── README.md
|
||||
│ └── CHANGELOG.md
|
||||
|
||||
### Next Planned (v0.1.3)
|
||||
- Introduce footer renderer with subpage counter and colored background band
|
||||
- Add configuration-driven page rotation logic
|
||||
- Implement "page tuning" animation for realism (Ceefax-style search effect)
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
## [v0.1.1] — Header Alignment Prototype (2025-11-02)
|
||||
- Implemented basic header text alignment
|
||||
- Added real-time clock display without background colour support
|
||||
- Began refinement of grid-safe text spacing
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
## [v0.1.0] — Initial Python Framework (2025-11-01)
|
||||
- Core grid model (TelefactFrame) defines 40×24 matrix
|
||||
- Base TelefactRenderer with safe-area calibration
|
||||
- Preliminary test layout with header, body, and footer
|
||||
- Initial config.json support for font, size, and colour
|
||||
119
docs/README.md
Normal file
119
docs/README.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Telefact — Python Edition
|
||||
|
||||
Telefact is a modern re-creation of a Ceefax-style Teletext broadcasting system, written in Python using the Tkinter library.
|
||||
It emulates the visual and structural behaviour of broadcast Teletext systems, maintaining a strict 40×24 character grid layout.
|
||||
This version focuses on the "Broadcaster" mode, replicating the look and timing of early digital information services such as BBC Ceefax and Channel 4 Oracle.
|
||||
|
||||
------------------------------------------------------------
|
||||
VERSION INFORMATION
|
||||
------------------------------------------------------------
|
||||
Version: v0.1.2
|
||||
Language: Python 3.13
|
||||
Interface: Tkinter (standard library)
|
||||
Target: Desktop application (Linux / Windows)
|
||||
Resolution: 40×24 Teletext grid
|
||||
Font: Modeseven (Teletext recreation)
|
||||
|
||||
------------------------------------------------------------
|
||||
PROJECT GOALS
|
||||
------------------------------------------------------------
|
||||
- Faithfully reproduce the look and feel of classic Teletext pages.
|
||||
- Maintain accurate grid alignment for all text and graphics.
|
||||
- Provide a configurable header, footer, and body layout.
|
||||
- Simulate real broadcast elements such as page numbers and timestamps.
|
||||
- Establish the foundation for future expansion into dynamic page rotation and live content updates.
|
||||
|
||||
------------------------------------------------------------
|
||||
CURRENT FEATURES
|
||||
------------------------------------------------------------
|
||||
- 40×24 Teletext grid rendering via TelefactRenderer.
|
||||
- Safe-area margins to simulate CRT overscan.
|
||||
- Dynamic Ceefax-style header displaying:
|
||||
- Selected page number (e.g., P100)
|
||||
- Service provider name block (e.g., Telefact)
|
||||
- Current page number (e.g., 100)
|
||||
- Real-time clock (updates every second)
|
||||
- Configurable colours and font settings via config.json.
|
||||
- Keyboard shortcuts:
|
||||
- Q or ESC to exit the application.
|
||||
|
||||
------------------------------------------------------------
|
||||
SOURCE STRUCTURE
|
||||
------------------------------------------------------------
|
||||
telefact/
|
||||
├── src/
|
||||
│ ├── core/
|
||||
│ │ ├── telefact_frame.py
|
||||
│ │ ├── telefact_header.py
|
||||
│ │ └── telefact_formatter.py
|
||||
│ ├── telefact_renderer.py
|
||||
│ └── config_manager.py
|
||||
├── docs/
|
||||
│ ├── README.md
|
||||
│ └── CHANGELOG.md
|
||||
├── assets/
|
||||
│ └── fonts/
|
||||
│ ├── Modeseven.ttf
|
||||
│ ├── EuropeanTeletext.ttf
|
||||
│ └── EuropeanTeletextNuevo.ttf
|
||||
└── main.py
|
||||
|
||||
------------------------------------------------------------
|
||||
CONFIGURATION OVERVIEW
|
||||
------------------------------------------------------------
|
||||
The configuration file is located at:
|
||||
config/config.json
|
||||
|
||||
Example:
|
||||
{
|
||||
"Mode": "Broadcaster",
|
||||
"ScreenWidth": 800,
|
||||
"ScreenHeight": 600,
|
||||
"Font": {
|
||||
"Name": "Modeseven",
|
||||
"Path": "assets/fonts/Modeseven.ttf",
|
||||
"Size": 18
|
||||
},
|
||||
"ShowGrid": false,
|
||||
"Colours": {
|
||||
"Header": "blue",
|
||||
"Footer": "red",
|
||||
"Background": "black",
|
||||
"TextPrimary": "white",
|
||||
"TextAccent": "yellow",
|
||||
"Clock": "yellow"
|
||||
},
|
||||
"Header": {
|
||||
"ServiceName": "Telefact",
|
||||
"ServiceFG": "yellow",
|
||||
"ServiceBG": "blue",
|
||||
"SelectedPage": "P100",
|
||||
"CurrentPage": "100"
|
||||
}
|
||||
}
|
||||
|
||||
------------------------------------------------------------
|
||||
ROADMAP
|
||||
------------------------------------------------------------
|
||||
v0.1.3 — Footer System and Subpage Counter
|
||||
- Implement footer band with coloured background and subpage indicator.
|
||||
- Integrate page tuning animation (Ceefax-style).
|
||||
- Add configuration options for rotation speed and page timing.
|
||||
|
||||
v0.2.0 — Page System Expansion
|
||||
- Introduce multiple page definitions with rotation logic.
|
||||
- Implement Teletext page index and feed loading.
|
||||
- Begin adding dynamic content from local cache and static feeds.
|
||||
|
||||
v0.3.0 — Broadcaster Automation
|
||||
- Simulate channel loop broadcasting through scheduled pages.
|
||||
- Add simple playback manager for testing page timing.
|
||||
|
||||
------------------------------------------------------------
|
||||
AUTHOR AND COPYRIGHT
|
||||
------------------------------------------------------------
|
||||
Developed by Stu Leak
|
||||
Leak Technologies (2025)
|
||||
All rights reserved.
|
||||
|
||||
Fonts: Modeseven and European Teletext by their respective creators.
|
||||
37
main.py
37
main.py
|
|
@ -1,33 +1,60 @@
|
|||
# ============================================================
|
||||
# File: /home/stu/Projects/Local REPO/telefact/main.py
|
||||
# Description:
|
||||
# Entry point for the Telefact Broadcaster mode.
|
||||
# Initializes the Tkinter window, loads configuration,
|
||||
# and renders the Teletext grid with dynamic header.
|
||||
# ============================================================
|
||||
|
||||
import tkinter as tk
|
||||
from src.config_manager import ConfigManager
|
||||
from src.telefact_renderer import TelefactRenderer
|
||||
from src.core.telefact_frame import TelefactFrame
|
||||
from src.core.telefact_formatter import TelefactFormatter
|
||||
from src.core.telefact_header import TelefactHeader
|
||||
|
||||
|
||||
def main():
|
||||
# --- Load configuration ---
|
||||
config = ConfigManager().config
|
||||
|
||||
# --- Initialize Tkinter window ---
|
||||
root = tk.Tk()
|
||||
root.title(f"Telefact — Broadcaster ({config['Mode']})")
|
||||
|
||||
# --- Create renderer ---
|
||||
renderer = TelefactRenderer(
|
||||
root,
|
||||
width=config["ScreenWidth"],
|
||||
height=config["ScreenHeight"],
|
||||
show_grid=config.get("ShowGrid", False),
|
||||
font_path=config["Font"]["Path"]
|
||||
font_path=config["Font"]["Path"],
|
||||
)
|
||||
|
||||
# --- Prepare frame ---
|
||||
frame = TelefactFrame()
|
||||
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")
|
||||
|
||||
# --- Initialize header ---
|
||||
header = TelefactHeader(frame, config)
|
||||
header.render()
|
||||
header.update_time(root, renderer)
|
||||
|
||||
# --- Body & footer test content ---
|
||||
formatter = TelefactFormatter(frame)
|
||||
formatter.add_body_line(1, "Welcome to the Telefact Broadcaster base.", align="center", color="white")
|
||||
formatter.add_body_line(3, "Press Q or ESC to exit.", align="center", color="cyan")
|
||||
formatter.set_footer("PAGE 100 TELEFACT", align="center", color="green")
|
||||
|
||||
# --- Render frame ---
|
||||
renderer.render(frame)
|
||||
|
||||
# --- Bind exit keys ---
|
||||
root.bind("<Escape>", lambda e: root.quit())
|
||||
root.bind("q", lambda e: root.quit())
|
||||
|
||||
# --- Start loop ---
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
Binary file not shown.
BIN
src/core/__pycache__/telefact_formatter.cpython-313.pyc
Normal file
BIN
src/core/__pycache__/telefact_formatter.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/core/__pycache__/telefact_header.cpython-313.pyc
Normal file
BIN
src/core/__pycache__/telefact_header.cpython-313.pyc
Normal file
Binary file not shown.
|
|
@ -0,0 +1,76 @@
|
|||
"""
|
||||
Telefact Formatter
|
||||
------------------
|
||||
Handles layout and text placement for header, body, and footer regions
|
||||
within a 40×24 Telefact frame.
|
||||
|
||||
This keeps Teletext-style alignment and line boundaries.
|
||||
"""
|
||||
|
||||
from typing import Literal
|
||||
from src.core.telefact_frame import TelefactFrame
|
||||
|
||||
|
||||
class TelefactFormatter:
|
||||
"""Provides structured access to write header, body, and footer text."""
|
||||
|
||||
def __init__(self, frame: TelefactFrame):
|
||||
self.frame = frame
|
||||
# Define region boundaries (row numbers)
|
||||
self.header_row = 0
|
||||
self.footer_row = frame.rows - 1
|
||||
self.body_start = 1
|
||||
self.body_end = self.footer_row - 1
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# text placement utilities
|
||||
# ------------------------------------------------------------------
|
||||
def _place_text(
|
||||
self,
|
||||
text: str,
|
||||
row: int,
|
||||
align: Literal["left", "center", "right"] = "left",
|
||||
color: str = "white",
|
||||
pad_char: str = " ",
|
||||
) -> None:
|
||||
"""Place text on a given row with Teletext-style alignment."""
|
||||
max_width = self.frame.cols
|
||||
text = text[:max_width] # clip overflow
|
||||
|
||||
if align == "left":
|
||||
col_start = 0
|
||||
elif align == "center":
|
||||
col_start = max(0, (max_width - len(text)) // 2)
|
||||
else: # right
|
||||
col_start = max(0, max_width - len(text))
|
||||
|
||||
# pad to maintain clean grid line
|
||||
padded = text.ljust(max_width, pad_char)
|
||||
for i, ch in enumerate(padded):
|
||||
self.frame.set_cell(col_start + i, row, ch, color)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# header/body/footer helpers
|
||||
# ------------------------------------------------------------------
|
||||
def set_header(self, text: str, align="center", color="yellow") -> None:
|
||||
"""Write the header row (row 0)."""
|
||||
self._place_text(text, self.header_row, align, color)
|
||||
|
||||
def add_body_line(
|
||||
self, line_num: int, text: str, align="left", color="white"
|
||||
) -> None:
|
||||
"""Write a line in the body area (rows 1–22)."""
|
||||
target_row = self.body_start + line_num
|
||||
if self.body_start <= target_row <= self.body_end:
|
||||
self._place_text(text, target_row, align, color)
|
||||
|
||||
def set_footer(self, text: str, align="center", color="cyan") -> None:
|
||||
"""Write the footer row (row 23)."""
|
||||
self._place_text(text, self.footer_row, align, color)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# convenience
|
||||
# ------------------------------------------------------------------
|
||||
def clear(self) -> None:
|
||||
"""Clear the frame (fills with spaces)."""
|
||||
self.frame.clear()
|
||||
|
|
@ -1,42 +1,67 @@
|
|||
"""
|
||||
Telefact Frame (model)
|
||||
- Defines a 40×24 grid and logical regions.
|
||||
- No rendering here—pure data model.
|
||||
"""
|
||||
# ============================================================
|
||||
# File: /home/stu/Projects/Local REPO/telefact/src/core/telefact_frame.py
|
||||
# Description:
|
||||
# Telefact Frame (model)
|
||||
# Defines a 40×24 grid and logical regions for Teletext layout.
|
||||
# Each cell now supports both foreground and background colours.
|
||||
# This file contains no rendering logic — pure data model.
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TelefactFrame:
|
||||
def __init__(self, cols: int = 40, rows: int = 24):
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
|
||||
# Define Teletext-like regions
|
||||
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)]
|
||||
# grid[row][col] -> (char, fg_color, bg_color)
|
||||
self.grid = [
|
||||
[(" ", "white", "black") 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"):
|
||||
# --------------------------------------------------------
|
||||
# Basic cell operations
|
||||
# --------------------------------------------------------
|
||||
def set_cell(
|
||||
self,
|
||||
col: int,
|
||||
row: int,
|
||||
char: str,
|
||||
fg_color: str = "white",
|
||||
bg_color: str = "black",
|
||||
):
|
||||
"""Sets a character and both colours in a given grid cell."""
|
||||
if 0 <= col < self.cols and 0 <= row < self.rows and char:
|
||||
self.grid[row][col] = (char[0], color)
|
||||
self.grid[row][col] = (char[0], fg_color, bg_color)
|
||||
|
||||
def get_cell(self, col: int, row: int):
|
||||
"""Returns (char, fg, bg) for the given grid position."""
|
||||
if 0 <= col < self.cols and 0 <= row < self.rows:
|
||||
return self.grid[row][col]
|
||||
return (" ", "white")
|
||||
return (" ", "white", "black")
|
||||
|
||||
def clear(self, char: str = " ", color: str = "white"):
|
||||
def clear(self, char: str = " ", fg_color: str = "white", bg_color: str = "black"):
|
||||
"""Clears the entire frame to a given colour and character."""
|
||||
for r in range(self.rows):
|
||||
for c in range(self.cols):
|
||||
self.grid[r][c] = (char, color)
|
||||
self.grid[r][c] = (char, fg_color, bg_color)
|
||||
|
||||
# regions
|
||||
# --------------------------------------------------------
|
||||
# Region helpers
|
||||
# --------------------------------------------------------
|
||||
def header_region(self):
|
||||
"""Row indices for header region (top row)."""
|
||||
return range(0, self.header_rows)
|
||||
|
||||
def body_region(self):
|
||||
"""Row indices for main content area."""
|
||||
return range(self.header_rows, self.header_rows + self.body_rows)
|
||||
|
||||
def footer_region(self):
|
||||
"""Row indices for footer region (bottom row)."""
|
||||
return range(self.rows - self.footer_rows, self.rows)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
# ============================================================
|
||||
# File: /home/stu/Projects/Local REPO/telefact/src/core/telefact_header.py
|
||||
# Description:
|
||||
# Dynamic Telefact header renderer for broadcaster mode.
|
||||
# Displays page numbers, centered service name block with
|
||||
# colored background, and a live timestamp.
|
||||
# ============================================================
|
||||
|
||||
import tkinter as tk
|
||||
from datetime import datetime
|
||||
from src.core.telefact_frame import TelefactFrame
|
||||
|
||||
|
||||
class TelefactHeader:
|
||||
"""
|
||||
Final layout (Ceefax style):
|
||||
┌────────────────────────────────────────────────────────┐
|
||||
│ P100 █ Telefact █ 100 Nov03 12:53:10 │
|
||||
└────────────────────────────────────────────────────────┘
|
||||
"""
|
||||
|
||||
def __init__(self, frame: TelefactFrame, config: dict):
|
||||
self.frame = frame
|
||||
self.config = config
|
||||
|
||||
# --- Read config values ---
|
||||
colours = config.get("Colours", {})
|
||||
header_cfg = config.get("Header", {})
|
||||
|
||||
# Service name & colours
|
||||
self.service_name = header_cfg.get("ServiceName", "Telefact")
|
||||
self.service_fg = header_cfg.get("ServiceFG", colours.get("TextAccent", "yellow"))
|
||||
self.service_bg = header_cfg.get("ServiceBG", colours.get("Header", "blue"))
|
||||
|
||||
# Page numbers
|
||||
self.selected_page = header_cfg.get("SelectedPage", "P100")
|
||||
self.current_page = header_cfg.get("CurrentPage", "100")
|
||||
|
||||
# Colours
|
||||
self.text_white = colours.get("TextPrimary", "white")
|
||||
self.time_yellow = colours.get("Clock", "yellow")
|
||||
|
||||
# Layout
|
||||
self.padding = 1
|
||||
self.time_format = "%b%d %H:%M:%S"
|
||||
|
||||
# --------------------------------------------------------
|
||||
def _clear_header_row(self):
|
||||
"""Clears only the top row."""
|
||||
for c in range(self.frame.cols):
|
||||
self.frame.set_cell(c, 0, " ", self.text_white, "black")
|
||||
|
||||
def _draw_text(self, col: int, text: str, color: str) -> int:
|
||||
"""Draws text from a given column; returns new cursor position."""
|
||||
for i, ch in enumerate(text):
|
||||
pos = col + i
|
||||
if pos < self.frame.cols:
|
||||
self.frame.set_cell(pos, 0, ch, color, "black")
|
||||
return col + len(text)
|
||||
|
||||
def _draw_service_block(self, text: str, fg: str, bg: str, left_bound: int, right_bound: int):
|
||||
"""Draw centered service block with full blue background."""
|
||||
block = f"{' ' * self.padding}{text}{' ' * self.padding}"
|
||||
total_space = right_bound - left_bound
|
||||
start_col = left_bound + (total_space // 2) - (len(block) // 2)
|
||||
start_col = max(start_col, 0)
|
||||
end_col = start_col + len(block)
|
||||
|
||||
# --- Fill background cells properly ---
|
||||
for c in range(start_col, end_col):
|
||||
if 0 <= c < self.frame.cols:
|
||||
self.frame.set_cell(c, 0, " ", bg, bg)
|
||||
|
||||
# --- Overlay text with proper foreground and background ---
|
||||
for i, ch in enumerate(text):
|
||||
pos = start_col + i + self.padding
|
||||
if 0 <= pos < self.frame.cols:
|
||||
self.frame.set_cell(pos, 0, ch, fg, bg)
|
||||
|
||||
# --------------------------------------------------------
|
||||
def render(self):
|
||||
"""Renders header row with page numbers, service, and clock."""
|
||||
self._clear_header_row()
|
||||
|
||||
# Left: selected page
|
||||
col_left = 0
|
||||
col_left = self._draw_text(col_left, self.selected_page, self.text_white)
|
||||
col_left += 2 # spacing after
|
||||
|
||||
# Right: timestamp (always yellow)
|
||||
time_text = datetime.now().strftime(self.time_format)
|
||||
right_start = self.frame.cols - len(time_text)
|
||||
self._draw_text(right_start, time_text, self.time_yellow)
|
||||
|
||||
# Mid-right: current page number (white)
|
||||
current_page_len = len(self.current_page)
|
||||
current_page_start = right_start - (current_page_len + 3)
|
||||
self._draw_text(current_page_start, self.current_page, self.text_white)
|
||||
|
||||
# Center: service provider (blue block)
|
||||
self._draw_service_block(
|
||||
self.service_name,
|
||||
self.service_fg,
|
||||
self.service_bg,
|
||||
left_bound=col_left,
|
||||
right_bound=current_page_start - 1,
|
||||
)
|
||||
|
||||
# --------------------------------------------------------
|
||||
def update_time(self, root: tk.Tk, renderer, interval_ms: int = 1000):
|
||||
"""Updates time every second without redrawing other rows."""
|
||||
self.render()
|
||||
renderer.render(self.frame)
|
||||
root.after(interval_ms, lambda: self.update_time(root, renderer, interval_ms))
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
"""
|
||||
Telefact Renderer (Tkinter)
|
||||
- Renders a 40×24 Telefact grid within a safe-area margin.
|
||||
- No header/footer bands; for layout calibration only.
|
||||
- Pure Python (stdlib only).
|
||||
"""
|
||||
# ============================================================
|
||||
# File: /home/stu/Projects/Local REPO/telefact/src/telefact_renderer.py
|
||||
# Description:
|
||||
# Telefact Renderer (Tkinter)
|
||||
# Renders a 40×24 Telefact grid with full foreground/background
|
||||
# colour support, aligned inside a CRT-style safe area margin.
|
||||
# No header/footer bars — layout calibration only.
|
||||
# ============================================================
|
||||
|
||||
import os
|
||||
import tkinter as tk
|
||||
|
|
@ -11,15 +13,18 @@ from tkinter import font as tkfont
|
|||
from typing import Dict
|
||||
from src.core.telefact_frame import TelefactFrame
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Standard Teletext colour palette (8 basic colours)
|
||||
# ------------------------------------------------------------
|
||||
PALETTE: Dict[str, str] = {
|
||||
"black": "#000000",
|
||||
"blue": "#0000FF",
|
||||
"red": "#FF0000",
|
||||
"black": "#000000",
|
||||
"blue": "#0000FF",
|
||||
"red": "#FF0000",
|
||||
"magenta": "#FF00FF",
|
||||
"green": "#00FF00",
|
||||
"cyan": "#00FFFF",
|
||||
"yellow": "#FFFF00",
|
||||
"white": "#FFFFFF",
|
||||
"green": "#00FF00",
|
||||
"cyan": "#00FFFF",
|
||||
"yellow": "#FFFF00",
|
||||
"white": "#FFFFFF",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -39,27 +44,30 @@ class TelefactRenderer:
|
|||
self.height = height
|
||||
self.show_grid = show_grid
|
||||
|
||||
# Safe area for 40×24 grid (simulate CRT overscan)
|
||||
# Teletext dimensions
|
||||
self.cols, self.rows = 40, 24
|
||||
|
||||
# Safe area margins (simulate CRT overscan)
|
||||
self.margin_x = 24
|
||||
self.margin_y = 24
|
||||
self.inner_w = width - self.margin_x * 2
|
||||
self.inner_h = height - self.margin_y * 2
|
||||
|
||||
# Per-cell dimensions
|
||||
self.cell_w = self.inner_w // self.cols
|
||||
self.cell_h = self.inner_h // self.rows
|
||||
self.w = self.inner_w
|
||||
self.h = self.inner_h
|
||||
|
||||
# Colours
|
||||
# Base colours
|
||||
self.colors = {
|
||||
"Background": "black",
|
||||
"Grid": "#303030"
|
||||
"Grid": "#303030",
|
||||
}
|
||||
if colors:
|
||||
self.colors.update(colors)
|
||||
|
||||
# Canvas covers the whole window
|
||||
# Canvas setup
|
||||
self.canvas = tk.Canvas(
|
||||
root,
|
||||
width=self.width,
|
||||
|
|
@ -72,8 +80,9 @@ class TelefactRenderer:
|
|||
# Font setup
|
||||
self.font = self._load_font(font_path, font_size)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------
|
||||
def _load_font(self, font_path: str | None, font_size: int) -> tuple:
|
||||
"""Loads Teletext font or falls back to Courier New."""
|
||||
if font_path and os.path.exists(font_path):
|
||||
try:
|
||||
font_name = os.path.splitext(os.path.basename(font_path))[0]
|
||||
|
|
@ -91,23 +100,45 @@ class TelefactRenderer:
|
|||
print(f"[warn] Font path not found: {font_path}. Using Courier New.")
|
||||
return ("Courier New", font_size, "bold")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------
|
||||
def _gx(self, col: int) -> int:
|
||||
"""Grid X coordinate."""
|
||||
return self.margin_x + col * self.cell_w
|
||||
|
||||
def _gy(self, row: int) -> int:
|
||||
"""Grid Y coordinate."""
|
||||
return self.margin_y + row * self.cell_h
|
||||
|
||||
def _draw_char(self, col: int, row: int, char: str, color: str) -> None:
|
||||
"""Draw a single glyph."""
|
||||
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_cell(self, col: int, row: int, char: str, fg: str, bg: str) -> None:
|
||||
"""Draw a full cell (background + glyph)."""
|
||||
x = self._gx(col)
|
||||
y = self._gy(row)
|
||||
|
||||
# Draw background block
|
||||
self.canvas.create_rectangle(
|
||||
x,
|
||||
y,
|
||||
x + self.cell_w,
|
||||
y + self.cell_h,
|
||||
fill=PALETTE.get(bg, bg),
|
||||
outline=PALETTE.get(bg, bg),
|
||||
)
|
||||
|
||||
# Draw text glyph
|
||||
if char.strip():
|
||||
self.canvas.create_text(
|
||||
x + 2,
|
||||
y + self.cell_h // 2,
|
||||
anchor="w",
|
||||
text=char,
|
||||
font=self.font,
|
||||
fill=PALETTE.get(fg, fg),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _draw_grid(self) -> None:
|
||||
"""Debug overlay for exact cell spacing."""
|
||||
"""Optional debug overlay for cell alignment."""
|
||||
for c in range(self.cols + 1):
|
||||
x = self._gx(c)
|
||||
self.canvas.create_line(
|
||||
|
|
@ -119,24 +150,26 @@ class TelefactRenderer:
|
|||
self.margin_x, y, self.margin_x + self.w, y, fill=self.colors["Grid"]
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# ------------------------------------------------------------------
|
||||
def render(self, frame: TelefactFrame) -> None:
|
||||
"""Render just the text grid; no header/footer bars."""
|
||||
"""Renders the full Telefact grid (foreground + background)."""
|
||||
self.canvas.delete("all")
|
||||
|
||||
# Fill total background
|
||||
# Fill background
|
||||
self.canvas.create_rectangle(
|
||||
0, 0, self.width, self.height,
|
||||
0,
|
||||
0,
|
||||
self.width,
|
||||
self.height,
|
||||
fill=PALETTE.get(self.colors["Background"], self.colors["Background"]),
|
||||
width=0,
|
||||
)
|
||||
|
||||
# Draw Teletext cells
|
||||
# Draw all cells (bg first, then text)
|
||||
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)
|
||||
ch, fg, bg = frame.get_cell(col, row)
|
||||
self._draw_cell(col, row, ch, fg, bg)
|
||||
|
||||
if self.show_grid:
|
||||
self._draw_grid()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user