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

318 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)
play_queue_list = Gtk.TreeView(
model=self.state.play_queue_store,
reorderable=True,
headers_visible=False,
)
play_queue_list.get_style_context().add_class("background")
renderer = Gtk.CellRendererPixbuf()
renderer.set_fixed_size(55, 60)
column = Gtk.TreeViewColumn("", renderer)
column.set_cell_data_func(renderer, common.filename_to_pixbuf)
column.set_resizable(True)
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)
play_queue_list.append_column(column)
renderer = Gtk.CellRendererPixbuf(icon_name="view-more-symbolic")
renderer.set_fixed_size(45, 60)
view_more_column = Gtk.TreeViewColumn("", renderer)
view_more_column.set_resizable(True)
play_queue_list.append_column(view_more_column)
renderer = Gtk.CellRendererPixbuf(icon_name="window-close-symbolic")
renderer.set_fixed_size(45, 60)
close_column = Gtk.TreeViewColumn("", renderer)
close_column.set_resizable(True)
play_queue_list.append_column(close_column)
def on_play_queue_button_press(tree: Any, event: Gdk.EventButton) -> bool:
if event.button == 1:
clicked_path = tree.get_path_at_pos(event.x, event.y)[0]
song_idx = clicked_path.get_indices()[0]
tree_width = tree.get_allocation().width
if event.x > tree_width - close_column.get_width():
self.state.emit("songs-removed", [song_idx])
elif event.x > tree_width - close_column.get_width() - view_more_column.get_width():
common.show_play_queue_popover(self.state, play_queue_list, [clicked_path], event.x, event.y)
else:
self.state.emit("song-clicked", song_idx, [m[-1] for m in self.state.play_queue_store], {"no_reshuffle": True})
return True
return False
play_queue_list.connect("button-press-event", on_play_queue_button_press)
play_queue_scrollbox.add(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)