tags: qt python pyside6 memorisation title: Simple Flash Card Overlay App # Overlay Widget ```py from icecream import ic; ic.configureOutput(includeContext = True) import sys import math import re import os from PySide6.QtCore import * from PySide6.QtGui import * from PySide6.QtWidgets import * from PySide6.QtNetwork import * from datetime import datetime def clamp(x,a,b): return max(min(x,b),a) class Subtitles(QWidget): def handleUdp(self): try: while self.udpSocket.hasPendingDatagrams(): datagram = self.udpSocket.receiveDatagram(4096) data = datagram.data().data() self.process_data(data.decode()) except KeyboardInterrupt: print("Ctrl-C") QApplication.instance().quit() def process_data(self,data): data = data[:256].strip() cmd,*params = re.split(r"\s+",data,1) match cmd: case "quit": return QApplication.instance().quit() case "fontsize": try: new_font_size = int(params[0]) new_font_size = min(160,max(32,new_font_size)) self.fontsize = new_font_size self.font = QFont(self.fontname,self.fontsize) self.font.setBold(self.bold) self.font.setItalic(self.italic) self.update() return except Exception: ic("#fail",cmd,params) case "bold": if len(params) > 0: if params[0].lower() in ["true","yes","1"]: self.bold = True else: self.bold = False else: self.bold = not self.bold self.font.setBold(self.bold) self.update() return case "italic": if len(params) > 0: if params[0].lower() in ["true","yes","1"]: self.italic = True else: self.italic = False else: self.italic = not self.italic self.font.setItalic(self.italic) self.update() return case "fontname": try: self.fontname = params[0] self.font = QFont(self.fontname,self.fontsize) self.font.setBold(self.bold) self.font.setItalic(self.italic) self.update() return except Exception: ic("#fail",cmd,params) case "period": try: new_period = int(params[0]) new_period = min(300,max(5,new_period)) new_period *= 1000 self.subtime = new_period self.timer.setInterval(new_period) return except Exception: ic("#fail",cmd,params) case "tick": self.tick() def __init__(self,subtime,src,surround=True,fontsize=120): super().__init__() self.app = QApplication.instance() self.surround = surround self.subtime = subtime self.lines = src self.index = 0 port = 4005 self.udpSocket = QUdpSocket() self.udpSocket.readyRead.connect(self.handleUdp) self.udpSocket.bind(QHostAddress.Any,port) # attributes for display self.content = "" try: self.fontsize = int(os.getenv("FONTSIZE",fontsize)) except Exception: self.fontsize = 120 try: self.fontname = os.getenv("FONTNAME","Optima") except Exception: self.fontname = "Optima" self.bold = False self.italic = False self.font = QFont(self.fontname,self.fontsize) self.colors = [ QColor(100,255,100), QColor(255,100,100), QColor(100,100,255), QColor(255,255,100), QColor(100,255,255), QColor(255,100,255) ] self.coloridx = 0 self.ocolor = Qt.black self.pen = QPen(self.ocolor,self.fontsize/10) self.timer = QTimer(self) self.subtime = subtime self.timer.setInterval(self.subtime) screen = QGuiApplication.primaryScreen() geometry = screen.geometry() self.height = geometry.height() self.width = geometry.width() self.move(0,0) self.resize(self.width,self.height) self.inset = 40 self.hh = self.height - 2*self.inset self.ww = self.width - 2*self.inset self.perimeter = (self.hh+self.ww)*2 # window attributes self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.NoDropShadowWindowHint ) self.setAttribute(Qt.WA_TranslucentBackground) self.timer.timeout.connect(self.tick) if surround: self.timer2 = QTimer(self) self.timer2.setInterval(1000/30) self.timer2.timeout.connect(self.tick2) def start(self): self.timer.start() if self.surround: self.timer2.start() self.tick2() def tick(self): try: self.index = (self.index + 1) % len(self.lines) self.coloridx = (self.coloridx + 1) % len(self.colors) self.hide() self.show() self.update() except KeyboardInterrupt: print("Ctrl-C") QApplication.instance().quit() def tick2(self): try: dt = self.timer.interval() - self.timer.remainingTime() dx = dt / self.subtime dx = max(dx,0.0) self.dx = dx self.dl = dx * self.perimeter self.update() except KeyboardInterrupt: print("Ctrl-C") QApplication.instance().quit() def mousePressEvent(self,event): QApplication.instance().quit() def paintEvent(self,event): with QPainter(self) as p: rect = self.rect() self.drawPerimeter(p) p.setFont(self.font) p.setPen(self.pen) text = self.lines[self.index].strip() metrics = QFontMetrics(self.font) brect = metrics.boundingRect(text) nlines = math.ceil((brect.width()*1.2)/self.width) words = re.split(r"\s+",text) h = metrics.height() i = 0 curw = 0 x = 50 y = int(self.fontsize*1.5) gap = int(self.fontsize * 0.5) for word in words: wrect = metrics.boundingRect(word) if curw == 0: self.drawText(p,x,y+i*h,word) curw = wrect.width() + gap continue neww = curw + wrect.width() if neww >= self.width - 100: i += 1 curw = 0 self.drawText(p,x,y+i*h,word) curw = wrect.width() + gap continue self.drawText(p,x+curw,y+i*h,word) curw += wrect.width() + gap def drawPerimeter(self,p): if not self.surround: return i = self.inset w = self.ww w2 = w/2 h = self.hh dl = self.dl dx = self.dx pe = self.perimeter color = QColor() color.setHslF(0.33*(1-dx),1.0,0.5) p.setPen(QPen(color,30)) p.setBrush(Qt.NoBrush) path = QPainterPath() path.moveTo(i+w2,i) l = min(dl,w2) path.lineTo(i+w2+l,i) dl -= l if dl > 0: l = min(dl,h) path.lineTo(i+w,i+l) dl -= l if dl > 0: l = min(dl,w) path.lineTo(i+w-l,i+h) dl -= l if dl > 0: l = min(dl,h) path.lineTo(i,i+h-l) dl -= l if dl > 0: l = min(dl,w2) path.lineTo(i+l,i) p.drawPath(path) def drawText(self,p,x,y,text): path = QPainterPath() path.addText(x,y,self.font,text) # draw twice so that the black outline is _behind_ the fill p.setPen(self.pen) p.drawPath(path) p.setBrush(self.colors[self.coloridx]) p.setPen(Qt.NoPen) p.drawPath(path) ``` # Driver scripts ### Larger font and perimeter snake ```py #!/usr/bin/env python3 import sys import os from setproctitle import setproctitle; setproctitle("sub1") from subt import Subtitles as st args = sys.argv[1:] if len(args) == 0: if not os.path.exists("subt.txt"): print(f"{sys.argv[0]} subtitle_file.txt") print(f"use SUBTIME to set subtitle change period") exit(1) args = ["subt.txt"] ifn = args[0] if not os.path.isfile(ifn): print(f"{ifn} does not exist, or is not a file") exit(2) with open(ifn) as f: src = f.read().rstrip().splitlines() from PySide6.QtWidgets import QApplication subtime = int(os.getenv("SUBTIME",30000)) app = QApplication(sys.argv) thesubt = st(subtime,src) thesubt.show() thesubt.start() try: exit(app.exec()) except KeyboardInterrupt: print("Ctrl-C 2") ``` ### Smaller font and no perimeter snake ```py #!/usr/bin/env python3 import sys import os from setproctitle import setproctitle; setproctitle("sub2") from subt import Subtitles as st args = sys.argv[1:] if len(args) == 0: if not os.path.exists("subt.txt"): print(f"{sys.argv[0]} subtitle_file.txt") print(f"use SUBTIME to set subtitle change period") exit(1) args = ["subt.txt"] ifn = args[0] if not os.path.isfile(ifn): print(f"{ifn} does not exist, or is not a file") exit(2) with open(ifn) as f: src = f.read().rstrip().splitlines() from PySide6.QtWidgets import QApplication subtime = int(os.getenv("SUBTIME",30000)) app = QApplication(sys.argv) thesubt = st(subtime,src,surround=False,fontsize=40) thesubt.show() thesubt.start() try: exit(app.exec()) except KeyboardInterrupt: print("Ctrl-C 2") ```