290 lines
10 KiB
Python
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)
|