nowplaying: Add a Seeker to the Now Playing card
And wire it up to the Player through the Application. Implements: #45 (Create a new NowPlaying widget) Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
2ff03bba18
commit
000dbd7018
|
@ -44,6 +44,11 @@ class Application(Adw.Application):
|
||||||
def __load_path(self, src: GObject.GObject, path: pathlib.Path) -> None:
|
def __load_path(self, src: GObject.GObject, path: pathlib.Path) -> None:
|
||||||
self.__load_file(path)
|
self.__load_file(path)
|
||||||
|
|
||||||
|
def __on_seek(self, nowplay: nowplaying.Card, newpos: float) -> None:
|
||||||
|
"""Handle a seek event."""
|
||||||
|
self.player.seek(newpos)
|
||||||
|
self.mpris.player.seeked(newpos)
|
||||||
|
|
||||||
def __seek(self, player: mpris2.player.Player, offset: float) -> None:
|
def __seek(self, player: mpris2.player.Player, offset: float) -> None:
|
||||||
self.player.seek(self.player.position + offset)
|
self.player.seek(self.player.position + offset)
|
||||||
|
|
||||||
|
@ -78,13 +83,15 @@ class Application(Adw.Application):
|
||||||
playing.bind_property("autopause", self, "autopause",
|
playing.bind_property("autopause", self, "autopause",
|
||||||
GObject.BindingFlags.BIDIRECTIONAL)
|
GObject.BindingFlags.BIDIRECTIONAL)
|
||||||
for prop in ["title", "album", "artist", "album-artist",
|
for prop in ["title", "album", "artist", "album-artist",
|
||||||
"playing", "have-track"]:
|
"playing", "position", "duration", "have-track"]:
|
||||||
self.player.bind_property(prop, playing, prop)
|
self.player.bind_property(prop, playing, prop)
|
||||||
self.db.settings.bind_setting("now-playing.prefer-artist",
|
self.db.settings.bind_setting("now-playing.prefer-artist",
|
||||||
playing, "prefer-artist")
|
playing, "prefer-artist")
|
||||||
|
|
||||||
playing.connect("play", self.player.play)
|
playing.connect("play", self.player.play)
|
||||||
playing.connect("pause", self.player.pause)
|
playing.connect("pause", self.player.pause)
|
||||||
|
playing.connect("seek", self.__on_seek)
|
||||||
|
|
||||||
return playing
|
return playing
|
||||||
|
|
||||||
def build_window(self) -> window.Window:
|
def build_window(self) -> window.Window:
|
||||||
|
|
|
@ -57,6 +57,12 @@ class Player(dbus.Object):
|
||||||
changed = GLib.Variant("b", self.get_property(property))
|
changed = GLib.Variant("b", self.get_property(property))
|
||||||
self.properties_changed({property: changed})
|
self.properties_changed({property: changed})
|
||||||
|
|
||||||
|
def seeked(self, newpos: float) -> None:
|
||||||
|
"""Notify that the track position has changed."""
|
||||||
|
args = GLib.Variant.new_tuple(GLib.Variant("x", newpos))
|
||||||
|
self.dbus.emit_signal(None, dbus.OBJECT_PATH, self.interface.name,
|
||||||
|
"Seeked", args)
|
||||||
|
|
||||||
@GObject.Property
|
@GObject.Property
|
||||||
def Metadata(self) -> dict:
|
def Metadata(self) -> dict:
|
||||||
"""Metadata for the current Track."""
|
"""Metadata for the current Track."""
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from . import controls
|
from . import controls
|
||||||
|
from . import seeker
|
||||||
from . import tags
|
from . import tags
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,6 +14,8 @@ class Card(Gtk.Box):
|
||||||
album = GObject.Property(type=str)
|
album = GObject.Property(type=str)
|
||||||
artist = GObject.Property(type=str)
|
artist = GObject.Property(type=str)
|
||||||
album_artist = GObject.Property(type=str)
|
album_artist = GObject.Property(type=str)
|
||||||
|
duration = GObject.Property(type=float, default=1)
|
||||||
|
position = GObject.Property(type=float, default=0)
|
||||||
prefer_artist = GObject.Property(type=bool, default=True)
|
prefer_artist = GObject.Property(type=bool, default=True)
|
||||||
playing = GObject.Property(type=bool, default=False)
|
playing = GObject.Property(type=bool, default=False)
|
||||||
autopause = GObject.Property(type=int, default=-1, minimum=-1, maximum=99)
|
autopause = GObject.Property(type=int, default=-1, minimum=-1, maximum=99)
|
||||||
|
@ -27,6 +30,8 @@ class Card(Gtk.Box):
|
||||||
self._grid = Gtk.Grid()
|
self._grid = Gtk.Grid()
|
||||||
self._tags = tags.TagInfo()
|
self._tags = tags.TagInfo()
|
||||||
self._controls = controls.Controls()
|
self._controls = controls.Controls()
|
||||||
|
self._bottom_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
|
||||||
|
self._seeker = seeker.Scale(sensitive=False)
|
||||||
|
|
||||||
for prop in ["title", "album", "artist", "album-artist"]:
|
for prop in ["title", "album", "artist", "album-artist"]:
|
||||||
self.bind_property(prop, self._tags, prop)
|
self.bind_property(prop, self._tags, prop)
|
||||||
|
@ -34,14 +39,20 @@ class Card(Gtk.Box):
|
||||||
GObject.BindingFlags.BIDIRECTIONAL)
|
GObject.BindingFlags.BIDIRECTIONAL)
|
||||||
for prop in ["playing", "have-next", "have-previous", "have-track"]:
|
for prop in ["playing", "have-next", "have-previous", "have-track"]:
|
||||||
self.bind_property(prop, self._controls, prop)
|
self.bind_property(prop, self._controls, prop)
|
||||||
|
self.bind_property("have-track", self._seeker, "sensitive")
|
||||||
self.bind_property("autopause", self._controls, "autopause",
|
self.bind_property("autopause", self._controls, "autopause",
|
||||||
GObject.BindingFlags.BIDIRECTIONAL)
|
GObject.BindingFlags.BIDIRECTIONAL)
|
||||||
|
self.bind_property("duration", self._seeker, "duration")
|
||||||
|
self.bind_property("position", self._seeker, "position")
|
||||||
|
|
||||||
for sig in ["play", "pause", "previous", "next"]:
|
for sig in ["play", "pause", "previous", "next"]:
|
||||||
self._controls.connect(sig, self.__on_control, sig)
|
self._controls.connect(sig, self.__on_control, sig)
|
||||||
|
self._seeker.connect("change-value", self.__on_seek)
|
||||||
|
|
||||||
|
self._bottom_box.append(self._seeker)
|
||||||
self._grid.attach(self._tags, 0, 0, 1, 1)
|
self._grid.attach(self._tags, 0, 0, 1, 1)
|
||||||
self._grid.attach(self._controls, 1, 0, 1, 1)
|
self._grid.attach(self._controls, 1, 0, 1, 1)
|
||||||
|
self._grid.attach(self._bottom_box, 0, 1, 2, 1)
|
||||||
|
|
||||||
self.append(self._grid)
|
self.append(self._grid)
|
||||||
self.add_css_class("card")
|
self.add_css_class("card")
|
||||||
|
@ -49,6 +60,10 @@ class Card(Gtk.Box):
|
||||||
def __on_control(self, controls: controls.Controls, signal: str) -> None:
|
def __on_control(self, controls: controls.Controls, signal: str) -> None:
|
||||||
self.emit(signal)
|
self.emit(signal)
|
||||||
|
|
||||||
|
def __on_seek(self, seek: seeker.Scale, scroll: Gtk.ScrollType,
|
||||||
|
value: float) -> None:
|
||||||
|
self.emit("seek", value)
|
||||||
|
|
||||||
@GObject.Signal
|
@GObject.Signal
|
||||||
def play(self) -> None:
|
def play(self) -> None:
|
||||||
"""Signal that the Play button has been clicked."""
|
"""Signal that the Play button has been clicked."""
|
||||||
|
@ -64,3 +79,7 @@ class Card(Gtk.Box):
|
||||||
@GObject.Signal
|
@GObject.Signal
|
||||||
def next(self) -> None:
|
def next(self) -> None:
|
||||||
"""Signal that the Nause button has been clicked."""
|
"""Signal that the Nause button has been clicked."""
|
||||||
|
|
||||||
|
@GObject.Signal(arg_types=(float,))
|
||||||
|
def seek(self, newpos: float) -> None:
|
||||||
|
"""Signal that the user wants us to seek."""
|
||||||
|
|
|
@ -17,8 +17,18 @@ class TestNowPlaying(unittest.TestCase):
|
||||||
"""Test that the card has been initialized correctly."""
|
"""Test that the card has been initialized correctly."""
|
||||||
self.assertIsInstance(self.card, Gtk.Box)
|
self.assertIsInstance(self.card, Gtk.Box)
|
||||||
self.assertIsInstance(self.card._grid, Gtk.Grid)
|
self.assertIsInstance(self.card._grid, Gtk.Grid)
|
||||||
|
self.assertIsInstance(self.card._bottom_box, Gtk.Box)
|
||||||
|
|
||||||
|
self.assertEqual(self.card._bottom_box.get_orientation(),
|
||||||
|
Gtk.Orientation.HORIZONTAL)
|
||||||
|
self.assertEqual(self.card._bottom_box.get_spacing(), 0)
|
||||||
|
|
||||||
self.assertEqual(self.card.get_last_child(), self.card._grid)
|
self.assertEqual(self.card.get_last_child(), self.card._grid)
|
||||||
|
self.assertEqual(self.card._grid.get_child_at(0, 1),
|
||||||
|
self.card._bottom_box)
|
||||||
|
self.assertEqual(self.card._grid.get_child_at(1, 1),
|
||||||
|
self.card._bottom_box)
|
||||||
|
|
||||||
self.assertTrue(self.card.has_css_class("card"))
|
self.assertTrue(self.card.has_css_class("card"))
|
||||||
|
|
||||||
def test_prefer_artist(self):
|
def test_prefer_artist(self):
|
||||||
|
@ -60,6 +70,22 @@ class TestNowPlaying(unittest.TestCase):
|
||||||
self.card.emit(signal)
|
self.card.emit(signal)
|
||||||
handler.assert_called_with(self.card)
|
handler.assert_called_with(self.card)
|
||||||
|
|
||||||
|
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.card._seeker)
|
||||||
|
|
||||||
|
self.assertFalse(self.card._seeker.get_sensitive())
|
||||||
|
self.card.have_track = True
|
||||||
|
self.assertTrue(self.card._seeker.get_sensitive())
|
||||||
|
|
||||||
|
handler = unittest.mock.Mock()
|
||||||
|
self.card.connect("seek", handler)
|
||||||
|
self.card._seeker.emit("change-value", Gtk.ScrollType.JUMP, 10)
|
||||||
|
handler.assert_called_with(self.card, 10)
|
||||||
|
|
||||||
def test_playing(self):
|
def test_playing(self):
|
||||||
"""Test the 'playing' property."""
|
"""Test the 'playing' property."""
|
||||||
self.assertFalse(self.card.playing)
|
self.assertFalse(self.card.playing)
|
||||||
|
@ -85,3 +111,15 @@ class TestNowPlaying(unittest.TestCase):
|
||||||
self.card.autopause = 3
|
self.card.autopause = 3
|
||||||
self.assertEqual(self.card.autopause, 3)
|
self.assertEqual(self.card.autopause, 3)
|
||||||
self.assertEqual(self.card._controls.autopause, 3)
|
self.assertEqual(self.card._controls.autopause, 3)
|
||||||
|
|
||||||
|
def test_duration(self):
|
||||||
|
"""Test the 'duration' property."""
|
||||||
|
self.assertEqual(self.card.duration, 1)
|
||||||
|
self.card.duration = 10
|
||||||
|
self.assertEqual(self.card._seeker.duration, 10)
|
||||||
|
|
||||||
|
def test_position(self):
|
||||||
|
"""Test the 'position' property."""
|
||||||
|
self.assertEqual(self.card.position, 0)
|
||||||
|
self.card.position = 0.5
|
||||||
|
self.assertEqual(self.card._seeker.position, 0.5)
|
||||||
|
|
|
@ -114,7 +114,8 @@ class TestEmmental(unittest.TestCase):
|
||||||
self.application.player = emmental.audio.Player()
|
self.application.player = emmental.audio.Player()
|
||||||
win = self.application.build_window()
|
win = self.application.build_window()
|
||||||
|
|
||||||
for (property, value) in [("have-track", True), ("playing", True)]:
|
for (property, value) in [("have-track", True), ("playing", True),
|
||||||
|
("duration", 10), ("position", 5)]:
|
||||||
with self.subTest(property=property, value=value):
|
with self.subTest(property=property, value=value):
|
||||||
self.application.player.set_property(property, value)
|
self.application.player.set_property(property, value)
|
||||||
self.assertEqual(win.now_playing.get_property(property), value)
|
self.assertEqual(win.now_playing.get_property(property), value)
|
||||||
|
|
Loading…
Reference in New Issue