nowplaying: Add a Favorite button

This button is an ImageToggle configured to show a filled-in heart when
active and an outline when inactive. I added some icons from the Gnome
icon-library to represent the different states.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-07-22 16:59:46 -04:00
parent 011dbd114b
commit 00f6ee9238
5 changed files with 157 additions and 1 deletions

View File

@ -2,6 +2,7 @@
"""A card for displaying information about the currently playing track."""
from gi.repository import GObject
from gi.repository import Gtk
from .. import buttons
from . import artwork
from . import controls
from . import seeker
@ -16,6 +17,7 @@ class Card(Gtk.Box):
album = GObject.Property(type=str)
artist = GObject.Property(type=str)
album_artist = GObject.Property(type=str)
favorite = GObject.Property(type=bool, default=False)
duration = GObject.Property(type=float, default=1)
position = GObject.Property(type=float, default=0)
prefer_artist = GObject.Property(type=bool, default=True)
@ -25,6 +27,7 @@ class Card(Gtk.Box):
have_next = GObject.Property(type=bool, default=False)
have_previous = GObject.Property(type=bool, default=False)
have_track = GObject.Property(type=bool, default=False)
have_db_track = GObject.Property(type=bool, default=False)
def __init__(self):
"""Initialize a Now Playing Card."""
@ -34,6 +37,11 @@ class Card(Gtk.Box):
self._tags = tags.TagInfo()
self._controls = controls.Controls()
self._bottom_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
self._favorite = buttons.ImageToggle("heart-filled",
"heart-outline-thick-symbolic",
icon_size=Gtk.IconSize.LARGE,
has_frame=False, sensitive=False,
valign=Gtk.Align.CENTER)
self._seeker = seeker.Scale(sensitive=False)
self.bind_property("artwork", self._artwork, "filepath")
@ -43,9 +51,12 @@ class Card(Gtk.Box):
GObject.BindingFlags.BIDIRECTIONAL)
for prop in ["playing", "have-next", "have-previous", "have-track"]:
self.bind_property(prop, self._controls, prop)
self.bind_property("have-db-track", self._favorite, "sensitive")
self.bind_property("have-track", self._seeker, "sensitive")
self.bind_property("autopause", self._controls, "autopause",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("favorite", self._favorite, "active",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("duration", self._seeker, "duration")
self.bind_property("position", self._seeker, "position")
@ -53,6 +64,7 @@ class Card(Gtk.Box):
self._controls.connect(sig, self.__on_control, sig)
self._seeker.connect("change-value", self.__on_seek)
self._bottom_box.append(self._favorite)
self._bottom_box.append(self._seeker)
self._grid.attach(self._tags, 0, 0, 1, 1)
self._grid.attach(self._controls, 1, 0, 1, 1)

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16px"
viewBox="0 0 16 16"
width="16px"
version="1.1"
id="svg4"
sodipodi:docname="heart-filled-symbolic.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="64.1875"
inkscape:cx="7.2132425"
inkscape:cy="7.9922103"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M 11.846992,1.3381258 C 10.739505,1.3069913 9.6587032,1.7072876 8.8403184,2.458957 L 7.9819043,3.2284162 7.1190428,2.458957 C 5.9848681,1.4359756 4.3836807,1.1023946 2.933716,1.582752 1.4793038,2.0586596 0.39405571,3.2773417 0.08716061,4.7762307 -0.21973333,6.2751197 0.29620481,7.8229342 1.4392744,8.8370195 L 7.9819043,14.663563 14.520087,8.8370195 C 15.854408,7.6850539 16.343661,5.8303454 15.752111,4.1713377 15.160561,2.5123299 13.608298,1.3870501 11.846992,1.3381258 Z m 0,0"
fill="#222222"
id="path2"
style="stroke-width:1.13862" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16px"
viewBox="0 0 16 16"
width="16px"
version="1.1"
id="svg4"
sodipodi:docname="heart-filled.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="64.1875"
inkscape:cx="6.7925998"
inkscape:cy="7.9922103"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M 11.846992,1.3381258 C 10.739505,1.3069913 9.6587032,1.7072876 8.8403184,2.458957 L 7.9819043,3.2284162 7.1190428,2.458957 C 5.9848681,1.4359756 4.3836807,1.1023946 2.933716,1.582752 1.4793038,2.0586596 0.39405571,3.2773417 0.08716061,4.7762307 -0.21973333,6.2751197 0.29620481,7.8229342 1.4392744,8.8370195 L 7.9819043,14.663563 14.520087,8.8370195 C 15.854408,7.6850539 16.343661,5.8303454 15.752111,4.1713377 15.160561,2.5123299 13.608298,1.3870501 11.846992,1.3381258 Z m 0,0"
fill="#222222"
id="path2"
style="fill:#ed333b;fill-opacity:1;stroke-width:1.13862" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16px"
viewBox="0 0 16 16"
width="16px"
version="1.1"
id="svg4"
sodipodi:docname="heart-outline-thick-symbolic.svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="64.1875"
inkscape:cx="7.2132425"
inkscape:cy="7.9922103"
inkscape:window-width="2560"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M 11.84691,1.3381547 C 10.739427,1.3070204 9.6586301,1.7073149 8.8446967,2.458981 L 7.9818389,3.2284369 7.1234285,2.458981 C 5.9848113,1.4360041 4.3836308,1.1024245 2.9336724,1.5827798 1.4792666,2.0586853 0.39402324,3.2773622 0.08712944,4.7762447 -0.2197632,6.2751272 0.29617274,7.822935 1.4436848,8.8370159 L 7.9818389,14.663534 14.52444,8.8370159 C 15.854309,7.6850553 16.343559,5.8303549 15.752012,4.1713543 15.160465,2.5123537 13.608209,1.3870788 11.84691,1.3381547 Z M 4.432555,3.5620165 c 0.3335795,0.022238 0.6893974,0.160118 1.1697515,0.5915469 v 0.00445 l 2.3795324,2.121565 2.3884271,-2.1304598 0.01334,-0.013344 c 0.382504,-0.3469219 0.880649,-0.5337268 1.401034,-0.5203834 1.014081,0.026686 1.552255,0.5693086 1.823566,1.3209736 0.266863,0.7472176 0.191252,1.512226 -0.573756,2.1749365 l -0.01334,0.013343 -5.0392701,4.4922017 -5.025927,-4.4833057 -0.00445,-0.00445 C 2.3065437,6.5642301 2.2042458,6.150591 2.2042458,5.7058186 2.1997983,5.2610463 2.404393,4.7317669 2.7824504,4.309234 3.1560592,3.8911474 3.6586516,3.624284 4.0989765,3.5753599 4.2101693,3.5620165 4.3169147,3.5575679 4.432555,3.5620165 Z m 0,0"
fill="#222222"
id="path2"
style="stroke-width:1.13862" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -81,11 +81,35 @@ class TestNowPlaying(unittest.TestCase):
self.card.emit(signal)
handler.assert_called_with(self.card)
def test_favorite_button(self):
"""Test the favorite button."""
self.assertIsInstance(self.card._favorite,
emmental.buttons.ImageToggle)
self.assertEqual(self.card._bottom_box.get_first_child(),
self.card._favorite)
self.assertEqual(self.card._favorite.active_icon_name, "heart-filled")
self.assertEqual(self.card._favorite.inactive_icon_name,
"heart-outline-thick-symbolic")
self.assertEqual(self.card._favorite.icon_size, Gtk.IconSize.LARGE)
self.assertEqual(self.card._favorite.get_valign(), Gtk.Align.CENTER)
self.assertFalse(self.card._favorite.get_has_frame())
self.assertFalse(self.card._favorite.get_sensitive())
self.card.have_db_track = True
self.assertTrue(self.card._favorite.get_sensitive())
self.assertFalse(self.card.favorite)
self.card.favorite = True
self.assertTrue(self.card._favorite.active)
self.card._favorite.active = False
self.assertFalse(self.card.favorite)
def test_seeker(self):
"""Test the seeker widget."""
self.assertIsInstance(self.card._seeker,
emmental.nowplaying.seeker.Scale)
self.assertEqual(self.card._bottom_box.get_last_child(),
self.assertEqual(self.card._favorite.get_next_sibling(),
self.card._seeker)
self.assertFalse(self.card._seeker.get_sensitive())