Files
sublime-music/sublime_music/ui/player_controls/mobile.py
Benjamin Schaaf e1dcf8da4c WIP
2021-12-20 22:07:06 +11:00

329 lines
12 KiB
Python

from typing import Any, Callable, Dict, Optional, Set, Tuple
from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk, Pango, Handy
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.EventBox):
def __init__(self, state):
super().__init__(above_child=False)
self.get_style_context().add_class("background")
self.state = state
self.state.add_control(self)
action_bar = Gtk.ActionBar()
action_bar.pack_start(self.create_song_display())
buttons = Gtk.Stack(transition_type=Gtk.StackTransitionType.CROSSFADE)
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))
buttons.add(close_buttons)
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)
open_buttons.pack_start(self.menu_button, False, False, 5)
buttons.add(open_buttons)
def flap_reveal(*_):
if self.state.flap_open:
buttons.set_visible_child(open_buttons)
else:
buttons.set_visible_child(close_buttons)
self.state.play_queue_open = False
self.state.connect("notify::flap-open", flap_reveal)
action_bar.pack_end(buttons)
self.add(action_bar)
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):
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(Handy.Flap):
def __init__(self, state):
super().__init__(orientation=Gtk.Orientation.VERTICAL, fold_policy=Handy.FlapFoldPolicy.ALWAYS, vexpand=True, swipe_to_open=False)
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)
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.set_content(box)
play_queue_scrollbox = Gtk.ScrolledWindow(vexpand=True)
self.play_queue_list = Gtk.TreeView(
model=self.state.play_queue_store,
reorderable=True,
headers_visible=False,
)
self.play_queue_list.get_style_context().add_class("background")
# Album Art column. This function defines what image to use for the play queue
# song icon.
def filename_to_pixbuf(
column: Any,
cell: Gtk.CellRendererPixbuf,
model: Gtk.ListStore,
tree_iter: Gtk.TreeIter,
flags: Any,
):
cell.set_property("sensitive", model.get_value(tree_iter, 0))
filename = model.get_value(tree_iter, 1)
if not filename:
cell.set_property("icon_name", "")
return
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(filename, 50, 50, True)
# If this is the playing song, then overlay the play icon.
if model.get_value(tree_iter, 3):
play_overlay_pixbuf = GdkPixbuf.Pixbuf.new_from_file(
str(resolve_path("ui/images/play-queue-play.png"))
)
play_overlay_pixbuf.composite(
pixbuf, 0, 0, 50, 50, 0, 0, 1, 1, GdkPixbuf.InterpType.NEAREST, 200
)
cell.set_property("pixbuf", pixbuf)
renderer = Gtk.CellRendererPixbuf()
renderer.set_fixed_size(55, 60)
column = Gtk.TreeViewColumn("", renderer)
column.set_cell_data_func(renderer, filename_to_pixbuf)
column.set_resizable(True)
self.play_queue_list.append_column(column)
renderer = Gtk.CellRendererText(markup=True, ellipsize=Pango.EllipsizeMode.END)
column = Gtk.TreeViewColumn("", renderer, markup=2, sensitive=0)
column.set_expand(True)
self.play_queue_list.append_column(column)
renderer = Gtk.CellRendererPixbuf(icon_name="view-more-symbolic")
renderer.set_fixed_size(30, 24)
column = Gtk.TreeViewColumn("", renderer)
column.set_resizable(True)
self.play_queue_list.append_column(column)
renderer = Gtk.CellRendererPixbuf(icon_name="window-close-symbolic")
renderer.set_fixed_size(30, 24)
column = Gtk.TreeViewColumn("", renderer)
column.set_resizable(True)
self.play_queue_list.append_column(column)
play_queue_scrollbox.add(self.play_queue_list)
self.set_flap(play_queue_scrollbox)
def on_play_queue_open(*_):
if not self.get_child_visible():
self.set_reveal_flap(False)
return
self.set_reveal_flap(self.state.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)