151 lines
3.8 KiB
Python
151 lines
3.8 KiB
Python
from typing import Any, Callable, Optional, Sequence, Type
|
|
|
|
from talon import Module, app, cron, ctrl, settings, ui
|
|
from talon.canvas import Canvas
|
|
from talon.skia.canvas import Canvas as SkiaCanvas
|
|
from talon.skia.imagefilter import ImageFilter
|
|
from talon.types import Rect
|
|
|
|
mod = Module()
|
|
|
|
|
|
def setting(
|
|
name: str, type: Type, desc: str, *, default: Optional[Any] = None
|
|
) -> Callable[[], type]:
|
|
mod.setting(f"subtitles_{name}", type, default=default, desc=f"Subtitles: {desc}")
|
|
return lambda: settings.get(f"user.subtitles_{name}")
|
|
|
|
|
|
setting_show = setting(
|
|
"show",
|
|
bool,
|
|
"If true show (custom) subtitles",
|
|
default=False,
|
|
)
|
|
setting_screens = setting(
|
|
"screens",
|
|
str,
|
|
"Show on which screens: 'all', 'main', 'cursor', 'focus'",
|
|
)
|
|
setting_size = setting(
|
|
"size",
|
|
int,
|
|
"Subtitle size in pixels",
|
|
)
|
|
setting_color = setting(
|
|
"color",
|
|
str,
|
|
"Subtitle color",
|
|
)
|
|
setting_color_outline = setting(
|
|
"color_outline",
|
|
str,
|
|
"Subtitle outline color",
|
|
)
|
|
setting_timeout_per_char = setting(
|
|
"timeout_per_char",
|
|
int,
|
|
"For each character in the subtitle extend the timeout by this amount in ms",
|
|
)
|
|
setting_timeout_min = setting(
|
|
"timeout_min",
|
|
int,
|
|
"Minimum time for a subtitle to show in ms",
|
|
)
|
|
setting_timeout_max = setting(
|
|
"timeout_max",
|
|
int,
|
|
"Maximum time for a subtitle to show in ms",
|
|
)
|
|
setting_y = setting(
|
|
"y",
|
|
float,
|
|
"Percentage of screen hight to show subtitle at. 0=top, 1=bottom",
|
|
)
|
|
|
|
mod = Module()
|
|
canvases: list[Canvas] = []
|
|
|
|
|
|
def show_subtitle(text: str):
|
|
"""Show subtitle"""
|
|
if not setting_show():
|
|
return
|
|
clear_canvases()
|
|
screens = get_screens()
|
|
for screen in screens:
|
|
canvas = show_text_on_screen(screen, text)
|
|
canvases.append(canvas)
|
|
|
|
|
|
def get_screens() -> Sequence[ui.Screen]:
|
|
screen = setting_screens()
|
|
match screen:
|
|
case "main":
|
|
return [ui.main_screen()]
|
|
case "all":
|
|
return ui.screens()
|
|
case "cursor":
|
|
x, y = ctrl.mouse_pos()
|
|
return [ui.screen_containing(x, y)]
|
|
case "focus":
|
|
return [ui.active_window().screen]
|
|
case _:
|
|
raise ValueError(f"Unknown screen setting: {screen}")
|
|
|
|
|
|
def show_text_on_screen(screen: ui.Screen, text: str):
|
|
timeout = calculate_timeout(text)
|
|
canvas = Canvas.from_screen(screen)
|
|
canvas.register("draw", lambda c: on_draw(c, screen, text))
|
|
canvas.freeze()
|
|
cron.after(f"{timeout}ms", canvas.close)
|
|
return canvas
|
|
|
|
|
|
def on_draw(c: SkiaCanvas, screen: ui.Screen, text: str):
|
|
scale = screen.scale if app.platform != "mac" else 1
|
|
size = setting_size() * scale
|
|
rect = set_text_size_and_get_rect(c, size, text)
|
|
x = c.rect.center.x - rect.center.x
|
|
# Clamp coordinate to make sure entire text is visible
|
|
y = max(
|
|
min(
|
|
c.rect.y + setting_y() * c.rect.height + c.paint.textsize / 2,
|
|
c.rect.bot - rect.bot,
|
|
),
|
|
c.rect.top - rect.top,
|
|
)
|
|
|
|
c.paint.imagefilter = ImageFilter.drop_shadow(2, 2, 1, 1, "000000")
|
|
c.paint.style = c.paint.Style.FILL
|
|
c.paint.color = setting_color()
|
|
c.draw_text(text, x, y)
|
|
|
|
# Outline
|
|
c.paint.imagefilter = None
|
|
c.paint.style = c.paint.Style.STROKE
|
|
c.paint.color = setting_color_outline()
|
|
c.draw_text(text, x, y)
|
|
|
|
|
|
def calculate_timeout(text: str) -> int:
|
|
ms_per_char = setting_timeout_per_char()
|
|
ms_min = setting_timeout_min()
|
|
ms_max = setting_timeout_max()
|
|
return min(ms_max, max(ms_min, len(text) * ms_per_char))
|
|
|
|
|
|
def set_text_size_and_get_rect(c: SkiaCanvas, size: int, text: str) -> Rect:
|
|
while True:
|
|
c.paint.textsize = size
|
|
rect = c.paint.measure_text(text)[1]
|
|
if rect.width < c.width * 0.8:
|
|
return rect
|
|
size *= 0.9
|
|
|
|
|
|
def clear_canvases():
|
|
for canvas in canvases:
|
|
canvas.close()
|
|
canvases.clear()
|