1# Copyright (c) 2014-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org> 2# This program is free software: you can redistribute it and/or modify 3# it under the terms of the GNU General Public License as published by 4# the Free Software Foundation, either version 3 of the License, or 5# (at your option) any later version. 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU General Public License for more details. 10# You should have received a copy of the GNU General Public License 11# along with this program. If not, see <http://www.gnu.org/licenses/>. 12 13from gi.repository import Gtk, GObject, GLib, Handy 14 15from lollypop.helper_art import ArtBehaviour 16from lollypop.define import App, ArtSize, MARGIN_SMALL, MARGIN 17from lollypop.widgets_player_progress import ProgressPlayerWidget 18from lollypop.widgets_player_buttons import ButtonsPlayerWidget 19from lollypop.widgets_player_artwork import ArtworkPlayerWidget 20from lollypop.widgets_player_label import LabelPlayerWidget 21from lollypop.helper_size_allocation import SizeAllocationHelper 22from lollypop.helper_signals import SignalsHelper, signals 23from lollypop.utils import emit_signal 24 25 26class MiniPlayer(Handy.WindowHandle, SizeAllocationHelper, SignalsHelper): 27 """ 28 Mini player shown in folded window 29 """ 30 __gsignals__ = { 31 "revealed": (GObject.SignalFlags.RUN_FIRST, None, (bool,)), 32 } 33 34 @signals 35 def __init__(self): 36 """ 37 Init mini player 38 """ 39 Handy.WindowHandle.__init__(self) 40 SizeAllocationHelper.__init__(self) 41 self.__previous_artwork_id = None 42 self.__per_track_cover = App().settings.get_value( 43 "allow-per-track-cover") 44 self.get_style_context().add_class("black") 45 self.__box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) 46 self.__box.show() 47 self.__box.get_style_context().add_class("padding") 48 self.__revealer = Gtk.Revealer.new() 49 self.__revealer.show() 50 self.__revealer_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 51 MARGIN_SMALL) 52 self.__revealer_box.show() 53 bottom_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, MARGIN_SMALL) 54 bottom_box.show() 55 self.__progress_widget = ProgressPlayerWidget() 56 self.__progress_widget.show() 57 self.__progress_widget.set_vexpand(True) 58 self.__progress_widget.set_margin_start(MARGIN) 59 self.__progress_widget.set_margin_end(MARGIN) 60 buttons_widget = ButtonsPlayerWidget(["banner-button-big"]) 61 buttons_widget.show() 62 buttons_widget.update() 63 buttons_widget.set_property("valign", Gtk.Align.CENTER) 64 buttons_widget.set_margin_end(MARGIN_SMALL) 65 self.__artwork_widget = ArtworkPlayerWidget(ArtBehaviour.CROP_SQUARE) 66 self.__artwork_widget.show() 67 self.__artwork_widget.set_vexpand(True) 68 self.__artwork_widget.set_property("margin", MARGIN_SMALL) 69 self.__artwork_widget.get_style_context().add_class( 70 "transparent-cover-frame") 71 self.__label_box = Gtk.Box(Gtk.Orientation.HORIZONTAL, MARGIN) 72 self.__label_box.show() 73 self.__label_box.set_property("margin", MARGIN_SMALL) 74 self.__label_widget = LabelPlayerWidget(False, 9) 75 self.__label_widget.show() 76 self.__label_widget.update() 77 self.__label_widget.set_margin_start(MARGIN_SMALL) 78 self.__label_widget.set_margin_end(MARGIN_SMALL) 79 self.__artwork_button = Gtk.Image.new() 80 self.__artwork_button.show() 81 self.__label_box.pack_start(self.__artwork_button, False, False, 0) 82 self.__label_box.pack_start(self.__label_widget, False, False, 0) 83 button = Gtk.Button.new() 84 button.show() 85 button.get_style_context().add_class("banner-button-big") 86 button.set_property("halign", Gtk.Align.START) 87 button.set_property("valign", Gtk.Align.CENTER) 88 button.connect("clicked", self.__on_button_clicked) 89 button.set_image(self.__label_box) 90 button.set_margin_start(MARGIN_SMALL) 91 self.__background = Gtk.Image() 92 self.__background.show() 93 # Assemble UI 94 self.__box.pack_start(self.__revealer, False, True, 0) 95 self.__box.pack_start(bottom_box, False, False, 0) 96 bottom_box.pack_start(button, True, True, 0) 97 bottom_box.pack_end(buttons_widget, False, False, 0) 98 self.__revealer.add(self.__revealer_box) 99 self.__revealer_box.pack_start(self.__artwork_widget, False, True, 0) 100 self.__revealer_box.pack_end(self.__progress_widget, False, True, 0) 101 overlay = Gtk.Overlay.new() 102 overlay.show() 103 overlay.add(self.__background) 104 overlay.add_overlay(self.__box) 105 self.add(overlay) 106 return [ 107 (App().player, "current-changed", "_on_current_changed") 108 ] 109 110 def reveal(self, reveal): 111 """ 112 Reveal miniplayer 113 @param reveal as bool 114 """ 115 if reveal: 116 self.__update_progress_visibility() 117 self.__revealer.set_reveal_child(True) 118 emit_signal(self, "revealed", True) 119 self.__progress_widget.update() 120 size = min(ArtSize.FULLSCREEN, self.width // 2) 121 self.__artwork_widget.set_art_size(size, size) 122 self.__artwork_widget.update(True) 123 if App().lookup_action("miniplayer").get_state(): 124 self.__artwork_button.set_from_icon_name( 125 "view-fullscreen-symbolic", Gtk.IconSize.BUTTON) 126 else: 127 self.__artwork_button.set_from_icon_name( 128 "pan-down-symbolic", Gtk.IconSize.BUTTON) 129 else: 130 self.__revealer.set_reveal_child(False) 131 emit_signal(self, "revealed", False) 132 self.update_artwork() 133 self.__set_widgets_position() 134 135 def update_artwork(self): 136 """ 137 Update artwork 138 """ 139 if not self.revealed: 140 App().art_helper.set_album_artwork( 141 App().player.current_track.album, 142 ArtSize.SMALL, 143 ArtSize.SMALL, 144 self.__artwork_button.get_scale_factor(), 145 ArtBehaviour.CACHE | 146 ArtBehaviour.CROP_SQUARE, 147 self.__on_button_artwork) 148 149 def do_get_preferred_width(self): 150 """ 151 Force preferred width 152 """ 153 (min, nat) = Gtk.Bin.do_get_preferred_width(self) 154 # Allow resizing 155 return (0, nat) 156 157 def do_get_preferred_height(self): 158 """ 159 Force preferred height 160 """ 161 (min, nat) = self.__box.get_preferred_height() 162 return (min, min) 163 164 @property 165 def revealed(self): 166 """ 167 True if mini player revealed 168 @return bool 169 """ 170 return self.__revealer.get_reveal_child() 171 172####################### 173# PROTECTED # 174####################### 175 def _on_current_changed(self, player): 176 """ 177 Update artwork and labels 178 @param player as Player 179 """ 180 if player.current_track.id is None: 181 self.__on_artwork(None) 182 return 183 184 same_artwork = self.__previous_artwork_id ==\ 185 player.current_track.album.id and not self.__per_track_cover 186 if same_artwork: 187 return 188 self.__previous_artwork_id = App().player.current_track.album.id 189 allocation = self.get_allocation() 190 self.__artwork_widget.set_artwork( 191 allocation.width, 192 allocation.height, 193 self.__on_artwork, 194 ArtBehaviour.BLUR_HARD | ArtBehaviour.DARKER) 195 self.update_artwork() 196 197 def _handle_width_allocate(self, allocation): 198 """ 199 Handle artwork sizing 200 @param allocation as Gtk.Allocation 201 """ 202 if SizeAllocationHelper._handle_width_allocate(self, allocation): 203 # We use parent height because we may be collapsed 204 parent = self.get_parent() 205 if parent is None: 206 height = self.height 207 else: 208 height = parent.get_allocated_height() 209 if self.__revealer.get_reveal_child(): 210 size = min(ArtSize.FULLSCREEN, self.width // 2) 211 self.__artwork_widget.set_art_size(size, size) 212 self.__artwork_widget.update(True) 213 self.__artwork_widget.set_artwork( 214 allocation.width + 100, height + 100, 215 self.__on_artwork, 216 ArtBehaviour.BLUR_HARD | ArtBehaviour.DARKER) 217 218 def _handle_height_allocate(self, allocation): 219 """ 220 Handle artwork sizing 221 @param allocation as Gtk.Allocation 222 """ 223 if SizeAllocationHelper._handle_height_allocate(self, allocation): 224 # We use parent height because we may be collapsed 225 parent = self.get_parent() 226 if parent is None: 227 height = allocation.height 228 else: 229 height = parent.get_allocated_height() 230 self.__artwork_widget.set_artwork( 231 allocation.width + 100, height + 100, 232 self.__on_artwork, 233 ArtBehaviour.BLUR_HARD | ArtBehaviour.DARKER) 234 235####################### 236# PRIVATE # 237####################### 238 def __update_progress_visibility(self): 239 """ 240 Update progress bar visibility 241 """ 242 if App().player.current_track.id is not None: 243 self.__progress_widget.show() 244 else: 245 self.__progress_widget.hide() 246 247 def __set_widgets_position(self): 248 """ 249 Add label widget to wanted UI part 250 """ 251 parent = self.__label_widget.get_parent() 252 if parent is not None: 253 parent.remove(self.__label_widget) 254 if self.revealed: 255 self.__revealer_box.pack_start(self.__label_widget, False, True, 0) 256 else: 257 self.__label_box.pack_start(self.__label_widget, False, False, 0) 258 259 def __on_button_clicked(self, *ignore): 260 """ 261 Set revealer on/off 262 """ 263 if App().lookup_action("miniplayer").get_state(): 264 App().lookup_action("miniplayer").change_state( 265 GLib.Variant("b", False)) 266 else: 267 self.reveal(not self.revealed) 268 269 def __on_button_artwork(self, surface): 270 """ 271 Set artwork on button 272 @param surface as cairo.Surface 273 """ 274 self.__artwork_button.set_from_surface(surface) 275 del surface 276 277 def __on_artwork(self, surface): 278 """ 279 Set artwork 280 @param surface as str 281 """ 282 self.__background.set_from_surface(surface) 283 if surface is None: 284 self.__background.get_style_context().add_class("black") 285 else: 286 self.__background.get_style_context().remove_class("black") 287 del surface 288