Files
sublime-music/sublime_music/ui/player_controls/mobile.py
Benjamin Schaaf 6a68cd1c99 WIP
2022-01-09 17:51:19 +11:00

290 lines
10 KiB
Python

from typing import Any, Callable, Dict, Optional, Set, Tuple
from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk, Pango, Handy
import bleach
from .. import util
from ..common import IconButton, IconToggleButton, SpinnerImage, SpinnerPicture
from ...config import AppConfiguration
from ...util import resolve_path
from . import common
class MobileHandle(Gtk.ActionBar):
def __init__(self, state):
super().__init__()
self.get_style_context().add_class("background")
self.state = state
self.state.add_control(self)
self.pack_start(self.create_song_display())
buttons = Gtk.Overlay()
open_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, halign=Gtk.Align.END)
self.play_queue_button = IconToggleButton(
"view-list-symbolic",
"Open play queue",
relief=True,
icon_size=Gtk.IconSize.LARGE_TOOLBAR,
)
def play_queue_button_toggled(*_):
if self.play_queue_button.get_active() != self.state.play_queue_open:
self.state.play_queue_open = self.play_queue_button.get_active()
self.play_queue_button.connect("toggled", play_queue_button_toggled)
def on_play_queue_open(*_):
self.play_queue_button.set_active(self.state.play_queue_open)
self.state.connect("notify::play-queue-open", on_play_queue_open)
open_buttons.pack_start(self.play_queue_button, False, False, 5)
self.menu_button = IconButton(
"view-more-symbolic",
"Menu",
relief=True,
icon_size=Gtk.IconSize.LARGE_TOOLBAR)
def on_menu(*_):
x = self.menu_button.get_allocated_width() / 2
y = self.menu_button.get_allocated_height()
util.show_song_popover(
[self.state.play_queue_store[self.state.current_song_index][-1]],
x, y,
self.menu_button,
self.state.offline_mode)
self.menu_button.connect('clicked', on_menu)
open_buttons.pack_start(self.menu_button, False, False, 5)
buttons.add(open_buttons)
close_buttons = Handy.Squeezer(halign=Gtk.Align.END, homogeneous=False)
expanded_close_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
expanded_close_buttons.pack_start(common.create_prev_button(self.state), False, False, 5)
expanded_close_buttons.pack_start(common.create_play_button(self.state, large=False), False, False, 5)
expanded_close_buttons.pack_start(common.create_next_button(self.state), False, False, 5)
close_buttons.add(expanded_close_buttons)
close_buttons.add(common.create_play_button(self.state, large=False, halign=Gtk.Align.END))
close_buttons.get_style_context().add_class("background")
buttons.add_overlay(close_buttons)
def flap_reveal(*_):
progress = 1 - self.state.flap_reveal_progress
close_buttons.set_opacity(progress)
close_buttons.set_visible(progress > 0.05)
if progress < 0.8:
self.state.play_queue_open = False
self.state.connect("notify::flap-reveal-progress", flap_reveal)
self.pack_end(buttons)
def create_song_display(self):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, hexpand=True)
box.set_property('width-request', 200)
self.cover_art = SpinnerImage(
image_name="player-controls-album-artwork",
image_size=50,
)
box.pack_start(self.cover_art, False, False, 0)
details_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
def make_label(name: str) -> Gtk.Label:
return Gtk.Label(
name=name,
halign=Gtk.Align.START,
xalign=0,
use_markup=True,
ellipsize=Pango.EllipsizeMode.END,
)
self.song_title = make_label("song-title")
details_box.add(self.song_title)
self.artist_name = make_label("artist-name")
details_box.add(self.artist_name)
box.pack_start(details_box, False, False, 5)
return box
def update(self, app_config: AppConfiguration, force: bool = False):
empty_queue = len(app_config.state.play_queue) == 0
self.play_queue_button.set_sensitive(not empty_queue)
self.menu_button.set_sensitive(not empty_queue)
if empty_queue:
self.state.play_queue_open = False
if app_config.state.current_song is not None:
self.song_title.set_markup(bleach.clean(app_config.state.current_song.title))
# TODO (#71): use walrus once MYPY gets its act together
album = app_config.state.current_song.album
artist = app_config.state.current_song.artist
if artist:
self.artist_name.set_markup(bleach.clean(artist.name))
self.artist_name.show()
elif album:
self.artist_name.set_markup(bleach.clean(album.name))
self.artist_name.show()
else:
self.artist_name.set_markup("")
self.artist_name.hide()
else:
# Clear out the cover art and song tite if no song
self.song_title.set_markup("")
self.artist_name.set_markup("")
def set_cover_art(self, cover_art_filename: str, loading: bool):
self.cover_art.set_from_file(cover_art_filename)
self.cover_art.set_loading(loading)
class MobileFlap(Gtk.Stack):
def __init__(self, state):
super().__init__(
transition_type=Gtk.StackTransitionType.OVER_DOWN_UP, vexpand=True)
self.state = state
self.state.add_control(self)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True)
box.get_style_context().add_class("background")
box.pack_start(Gtk.Separator(), False, False, 0)
overlay = Gtk.Overlay(hexpand=True, vexpand=True, height_request=100)
self.cover_art = SpinnerPicture(image_name="player-controls-album-artwork")
overlay.add(self.cover_art)
overlay_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
overlay.add_overlay(overlay_box)
overlay_row_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
overlay_box.pack_end(overlay_row_box, False, False, 0)
overlay_row_box.pack_start(common.create_label(self.state, "progress-label"), False, False, 5)
overlay_row_box.pack_end(common.create_label(self.state, "duration-label"), False, False, 5)
# Device
self.device_button = IconButton(
"chromecast-symbolic",
"Show available audio output devices",
icon_size=Gtk.IconSize.LARGE_TOOLBAR,
)
def on_device_click(_: Any):
if self.device_popover.is_visible():
self.device_popover.popdown()
else:
self.device_popover.popup()
self.device_popover.show_all()
self.device_button.connect("clicked", self.state.popup_devices)
overlay_row_box.set_center_widget(self.device_button)
self.device_popover = Gtk.PopoverMenu(modal=False, name="device-popover")
self.device_popover.set_relative_to(self.device_button)
device_popover_box = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
name="device-popover-box",
)
device_popover_header = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.popover_label = Gtk.Label(
label="<b>Devices</b>",
use_markup=True,
halign=Gtk.Align.START,
margin=5,
)
device_popover_header.add(self.popover_label)
refresh_devices = IconButton("view-refresh-symbolic", "Refresh device list")
refresh_devices.set_action_name("app.refresh-devices")
device_popover_header.pack_end(refresh_devices, False, False, 0)
device_popover_box.add(device_popover_header)
device_list_and_loading = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.device_list = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
device_list_and_loading.add(self.device_list)
device_popover_box.pack_end(device_list_and_loading, True, True, 0)
self.device_popover.add(device_popover_box)
box.pack_start(overlay, True, True, 0)
# Song Scrubber
scrubber = Gtk.Scale(
orientation=Gtk.Orientation.HORIZONTAL,
adjustment=self.state.scrubber,
name="song-scrubber",
draw_value=False,
restrict_to_fill_level=False,
show_fill_level=True)
self.state.bind_property("scrubber-cache", scrubber, "fill-level")
box.pack_start(scrubber, False, False, 0)
buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
# Repeat button
repeat_button = common.create_repeat_button(self.state, valign=Gtk.Align.CENTER, margin_left=10)
buttons.pack_start(repeat_button, False, False, 0)
center_buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
# Previous button
center_buttons.add(common.create_prev_button(self.state, valign=Gtk.Align.CENTER))
# Play button
center_buttons.add(common.create_play_button(self.state))
# Next button
center_buttons.add(common.create_next_button(self.state, valign=Gtk.Align.CENTER))
buttons.set_center_widget(center_buttons)
# Shuffle button
shuffle_button = common.create_shuffle_button(self.state, valign=Gtk.Align.CENTER, margin_right=10)
buttons.pack_end(shuffle_button, False, False, 0)
box.pack_start(buttons, False, False, 0)
box.pack_start(Gtk.Box(), False, False, 5)
self.add(box)
play_queue_scrollbox = Gtk.ScrolledWindow(vexpand=True)
play_queue_scrollbox.add(common.create_play_queue_list(self.state))
self.add(play_queue_scrollbox)
def on_play_queue_open(*_):
if self.state.play_queue_open:
self.set_visible_child(play_queue_scrollbox)
else:
self.set_visible_child(box)
on_play_queue_open()
self.state.connect("notify::play-queue-open", on_play_queue_open)
def update(self, app_config: AppConfiguration, force: bool = False):
pass
def set_cover_art(self, cover_art_filename: str, loading: bool):
self.cover_art.set_from_file(cover_art_filename)
self.cover_art.set_loading(loading)