mirror of
https://git.srhtnin.garden/sarah/SaraTune.git
synced 2026-10-14 02:18:11 +02:00
Initial commit
This commit is contained in:
commit
0eeff23589
25 files changed
+3328
No files matched your search
+146
@@ -0,0 +1,146 @@
|
||||
"""The play bar at the bottom of both modes: what's playing, previous / play /
|
||||
next, the seek bar and volume. Each mode adds its own toggles (loop, shuffle,
|
||||
repeat) through Extras."""
|
||||
|
||||
import asyncio
|
||||
from typing import Awaitable, Callable, Optional
|
||||
|
||||
import flet as ft
|
||||
|
||||
from Core import FormatTime, Palette
|
||||
from Player import Player
|
||||
|
||||
CoverSize = 52
|
||||
|
||||
|
||||
def CoverImage(Data: Optional[bytes], Size: int, Radius: int = 4) -> ft.Control:
|
||||
"""A cover, or a pixel-style note on the theme's colours when there's none."""
|
||||
if Data:
|
||||
return ft.Image(src=Data, width=Size, height=Size, fit=ft.BoxFit.COVER,
|
||||
border_radius=Radius, filter_quality=ft.FilterQuality.MEDIUM, gapless_playback=True)
|
||||
return ft.Container(
|
||||
ft.Icon(ft.Icons.MUSIC_NOTE, color=Palette.Mauve, size=Size * 0.5),
|
||||
width=Size, height=Size, bgcolor=Palette.Surface0, border_radius=Radius,
|
||||
border=ft.Border.all(1, Palette.Surface1), alignment=ft.Alignment.CENTER,
|
||||
)
|
||||
|
||||
|
||||
class Transport(ft.Container):
|
||||
def __init__(self, ThePlayer: Player, OnPrevious: Callable[[], Awaitable[None]],
|
||||
OnNext: Callable[[], Awaitable[None]], OnVolume: Callable[[float], None],
|
||||
Extras: Optional[list[ft.Control]] = None, OnInfoClick: Optional[Callable] = None):
|
||||
super().__init__(
|
||||
bgcolor=Palette.Mantle,
|
||||
border=ft.Border.only(top=ft.BorderSide(1, Palette.Surface1)),
|
||||
padding=ft.Padding.symmetric(horizontal=12, vertical=8),
|
||||
)
|
||||
self.ThePlayer = ThePlayer
|
||||
self._Dragging = False
|
||||
self.Shown = False # set by Main: only the shown mode's bar is on the page
|
||||
|
||||
self.Cover = ft.Container(CoverImage(None, CoverSize))
|
||||
self.Title = ft.Text("Nothing playing", color=Palette.Text, size=15, max_lines=1,
|
||||
overflow=ft.TextOverflow.ELLIPSIS)
|
||||
self.Subtitle = ft.Text("", color=Palette.Overlay0, size=12, max_lines=1, overflow=ft.TextOverflow.ELLIPSIS)
|
||||
Info = ft.Container(
|
||||
ft.Row([self.Cover, ft.Column([self.Title, self.Subtitle], spacing=2, expand=True, tight=True)],
|
||||
spacing=10),
|
||||
width=280, on_click=OnInfoClick, ink=OnInfoClick is not None, border_radius=4,
|
||||
)
|
||||
|
||||
async def Previous(e) -> None:
|
||||
await OnPrevious()
|
||||
|
||||
async def Next(e) -> None:
|
||||
await OnNext()
|
||||
|
||||
async def Toggle(e) -> None:
|
||||
await ThePlayer.Toggle()
|
||||
|
||||
self.PlayButton = ft.IconButton(
|
||||
ft.Icons.PLAY_ARROW_ROUNDED, icon_size=34, icon_color=Palette.Crust, bgcolor=Palette.Mauve,
|
||||
tooltip="Play / pause (space)", on_click=Toggle,
|
||||
)
|
||||
Buttons = ft.Row([
|
||||
ft.IconButton(ft.Icons.SKIP_PREVIOUS_ROUNDED, icon_color=Palette.Text, tooltip="Previous", on_click=Previous),
|
||||
self.PlayButton,
|
||||
ft.IconButton(ft.Icons.SKIP_NEXT_ROUNDED, icon_color=Palette.Text, tooltip="Next", on_click=Next),
|
||||
], spacing=4)
|
||||
|
||||
self.Elapsed = ft.Text("0:00", color=Palette.Overlay0, size=12, width=52, text_align=ft.TextAlign.RIGHT)
|
||||
self.Total = ft.Text("0:00", color=Palette.Overlay0, size=12, width=52)
|
||||
self.Seek = ft.Slider(
|
||||
min=0, max=1, value=0, expand=True, active_color=Palette.Mauve, inactive_color=Palette.Surface1,
|
||||
thumb_color=Palette.Mauve, on_change_start=self._DragStart, on_change=self._Dragged,
|
||||
on_change_end=self._DragEnd,
|
||||
)
|
||||
|
||||
def VolumeChanged(e) -> None:
|
||||
OnVolume(e.control.value)
|
||||
self.VolumeIcon.icon = self._VolumeIconFor(e.control.value)
|
||||
self.VolumeIcon.update()
|
||||
|
||||
self.VolumeIcon = ft.Icon(self._VolumeIconFor(ThePlayer.Volume), color=Palette.Overlay0, size=20)
|
||||
self.Volume = ft.Slider(min=0, max=1, value=ThePlayer.Volume, width=120, active_color=Palette.Mauve,
|
||||
inactive_color=Palette.Surface1, thumb_color=Palette.Mauve, on_change=VolumeChanged)
|
||||
|
||||
self.content = ft.Row([
|
||||
Info,
|
||||
Buttons,
|
||||
self.Elapsed, self.Seek, self.Total,
|
||||
*(Extras or []),
|
||||
self.VolumeIcon, self.Volume,
|
||||
], spacing=8, vertical_alignment=ft.CrossAxisAlignment.CENTER)
|
||||
|
||||
@staticmethod
|
||||
def _VolumeIconFor(Value: float):
|
||||
return ft.Icons.VOLUME_OFF if Value <= 0 else ft.Icons.VOLUME_DOWN if Value < 0.5 else ft.Icons.VOLUME_UP
|
||||
|
||||
def SetVolume(self, Value: float) -> None:
|
||||
"""The other mode's bar changed the shared volume."""
|
||||
self.Volume.value = Value
|
||||
self.VolumeIcon.icon = self._VolumeIconFor(Value)
|
||||
|
||||
def ShowTrack(self, Title: str, Subtitle: str, Cover: Optional[bytes]) -> None:
|
||||
self.Title.value = Title
|
||||
self.Subtitle.value = Subtitle
|
||||
self.Cover.content = CoverImage(Cover, CoverSize)
|
||||
if self.Shown:
|
||||
self.update()
|
||||
|
||||
# --- Seek bar -----------------------------------------------------------
|
||||
|
||||
def _DragStart(self, e) -> None:
|
||||
self._Dragging = True
|
||||
|
||||
def _Dragged(self, e) -> None:
|
||||
self.Elapsed.value = FormatTime(e.control.value)
|
||||
self.Elapsed.update()
|
||||
|
||||
async def _DragEnd(self, e) -> None:
|
||||
self._Dragging = False
|
||||
await self.ThePlayer.Seek(int(e.control.value))
|
||||
|
||||
def Refresh(self) -> None:
|
||||
"""Play button, times and seek bar from the player; called by a
|
||||
ticker a few times a second while playing."""
|
||||
P = self.ThePlayer
|
||||
self.PlayButton.icon = ft.Icons.PAUSE_ROUNDED if P.Playing else ft.Icons.PLAY_ARROW_ROUNDED
|
||||
Duration = max(P.Duration, 1)
|
||||
self.Seek.max = Duration
|
||||
self.Seek.disabled = not P.Source
|
||||
self.Total.value = FormatTime(P.Duration)
|
||||
if not self._Dragging:
|
||||
self.Seek.value = min(P.Position, Duration)
|
||||
self.Elapsed.value = FormatTime(P.Position)
|
||||
if self.Shown:
|
||||
self.update()
|
||||
|
||||
|
||||
async def Ticker(Bars: list[Transport], Interval: float = 0.25) -> None:
|
||||
"""Keeps the playing bars moving. Runs for the app's lifetime."""
|
||||
while True:
|
||||
for Bar in Bars:
|
||||
if Bar.ThePlayer.Playing and Bar.Shown:
|
||||
Bar.Refresh()
|
||||
await asyncio.sleep(Interval)
|
||||
Reference in new issue
Block a user