This commit is contained in:
Andrew Dunai 2018-01-02 11:26:55 +02:00
parent 77e3f44fa5
commit 5b88ae681a
7 changed files with 160 additions and 51 deletions

31
.flake8 Normal file
View file

@ -0,0 +1,31 @@
[flake8]
# ignore = D203
max-line-length=120
ignore =
# blank line at end of file
W391
exclude =
# Generated stuff
gen,
# Coverage HTML report
htmlcov,
# Logs
log,
# Requirements
requirements,
# Temporary stuff
tmp,
# Virtual envs
.env,
.env2,
.env3,
.env36,
.venv,
.git,
wsgi.py,
utils,
# VLC bindings
vlc.py

View file

@ -1,2 +0,0 @@
from clay.app import main

View file

@ -1,23 +1,26 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# pylint: disable=wrong-import-position
# pylint: disable=too-many-instance-attributes
"""
Main app entrypoint.
"""
import sys
sys.path.append('.')
sys.path.append('.') # noqa
import urwid
from clay.player import player
from clay.widgets import PlayProgress
from clay.pages import StartUp, Error
from clay.pages import StartUp
from clay.mylibrary import MyLibrary
from clay.myplaylists import MyPlaylists
from clay.playerqueue import Queue
from clay.settings import Settings
from clay.notifications import NotificationArea
# from clay import hotkeys
loop = None
PALETTE = [
('logo', '', '', '', '#F54', ''),
@ -45,24 +48,39 @@ PALETTE = [
('flag', '', '', '', '#AAA', ''),
('flag-active', '', '', '', '#F54', ''),
('notification', '', '', '', '#F54', '#222'),
]
class AppWidget(urwid.Frame):
"""
Root widget.
Handles tab switches, global keypresses etc.
"""
class Tab(urwid.Text):
"""
Represents a single tab in header tabbar.
"""
def __init__(self, page):
self.page = page
# self.attrwrap = urwid.AttrWrap(urwid.Text(), 'panel')
super(AppWidget.Tab, self).__init__(
self.get_title()
)
def set_active(self, active):
"""
Mark tab visually as active.
"""
self.set_text(
[('panel_focus' if active else 'panel', self.get_title())]
)
def get_title(self):
"""
Render tab title.
"""
return ' {} {} '.format(
self.page.key,
self.page.name
@ -71,7 +89,6 @@ class AppWidget(urwid.Frame):
def __init__(self):
self.pages = [
StartUp(self),
Error(self),
MyLibrary(self),
MyPlaylists(self),
Queue(self),
@ -83,6 +100,7 @@ class AppWidget(urwid.Frame):
in self.pages
if hasattr(page, 'key')
]
NotificationArea.set_app(self)
self.header = urwid.Pile([
# urwid.Divider('\u2500'),
urwid.AttrWrap(urwid.Columns([
@ -90,6 +108,7 @@ class AppWidget(urwid.Frame):
for tab
in self.tabs
], dividechars=1), 'panel'),
NotificationArea.get()
# urwid.Divider('\u2500')
])
self.seekbar = PlayProgress(
@ -116,27 +135,48 @@ class AppWidget(urwid.Frame):
body=self.current_page
)
self.loop = None
player.media_position_changed += self.media_position_changed
player.media_state_changed += self.media_state_changed
player.track_changed += self.track_changed
player.playback_flags_changed += self.playback_flags_changed
def set_loop(self, loop):
"""
Assign a MainLoop to this app.
"""
self.loop = loop
def media_position_changed(self, progress):
"""
Update slider in seekbar.
Called when current play position changes.
"""
if progress < 0:
progress = 0
self.seekbar.set_completion(progress * 100)
loop.draw_screen()
# sleep(0.2)
self.loop.draw_screen()
# self.set_page(MyLibrary())
def media_state_changed(self, is_playing):
def media_state_changed(self, _):
"""
Update seekbar.
Called when playback is paused/unpaused.
"""
self.seekbar.update()
def track_changed(self, track):
"""
Update displayed track in seekbar.
Called when current track changes.
"""
self.seekbar.set_track(track)
def playback_flags_changed(self):
"""
Update seekbar flags.
Called when random/repeat flags change.
"""
self.shuffle_el.attr = 'flag-active' \
if player.get_is_random() \
else 'flag'
@ -145,15 +185,10 @@ class AppWidget(urwid.Frame):
else 'flag'
self.seekbar.update()
def set_page(self, classname, *args):
# if isinstance(page_class, str):
# page_class = [
# page
# for page
# in self.pages
# if page.__name__ == page_class
# ][0]
# self.current_page = page_class(self, *args)
def set_page(self, classname):
"""
Switch to a different tab.
"""
page = [page for page in self.pages if page.__class__.__name__ == classname][0]
self.current_page = page
self.contents['body'] = (page, None)
@ -169,12 +204,18 @@ class AppWidget(urwid.Frame):
page.start()
def redraw(self):
if loop:
loop.draw_screen()
"""
Redraw screen.
Needs to be called by other widgets if UI was changed from a different thread.
"""
if self.loop:
self.loop.draw_screen()
def keypress(self, size, key):
if isinstance(self.current_page, StartUp):
return
"""
Handle keypress.
Can switch tabs, control playbackm, flags, notifications and app state.
"""
for tab in self.tabs:
if 'meta {}'.format(tab.page.key) == key:
self.set_page(tab.page.__class__.__name__)
@ -196,18 +237,22 @@ class AppWidget(urwid.Frame):
player.set_repeat_one(not player.get_is_repeat_one())
elif key == 'ctrl q':
sys.exit(0)
elif key == 'esc':
NotificationArea.close_all()
else:
super(AppWidget, self).keypress(size, key)
def main():
global loop
"""
Application entrypoint.
"""
app_widget = AppWidget()
loop = urwid.MainLoop(app_widget, PALETTE)
app_widget.set_loop(loop)
loop.screen.set_terminal_properties(256)
loop.run()
if __name__ == '__main__':
main()

View file

@ -15,7 +15,6 @@ def async(fn):
result = fn(*args, **kwargs)
except Exception as e:
callback(None, e, **extra)
raise
else:
callback(result, None, **extra)
@ -147,5 +146,6 @@ class GP(object):
def is_authenticated(self):
return self.mc.is_authenticated()
gp = GP()

50
clay/notifications.py Normal file
View file

@ -0,0 +1,50 @@
import urwid
class NotificationArea(urwid.Pile):
instance = None
app = None
TEMPLATE = ' \u26A1 {}'
def __init__(self):
super().__init__([])
@classmethod
def get(cls):
if cls.instance is None:
cls.instance = NotificationArea()
return cls.instance
@classmethod
def set_app(cls, app):
cls.app = app
@classmethod
def notify(cls, message):
cls.instance._notify(message)
@classmethod
def close_all(cls):
cls.instance._close_all()
def _notify(self, message):
text = urwid.Text(self.__class__.TEMPLATE.format(message))
self.contents.append(
(
urwid.AttrWrap(
urwid.Columns([
text,
('pack', urwid.Text('[Hit ESC to close] '))
]),
'notification'
),
('weight', 1)
)
)
self.__class__.app.redraw()
def _close_all(self):
self.contents[:] = []

View file

@ -2,6 +2,7 @@ import urwid
from clay.settings import Settings
from clay.gp import gp
from clay.meta import VERSION
from clay.notifications import NotificationArea
class StartUp(urwid.Filler):
@ -29,14 +30,11 @@ class StartUp(urwid.Filler):
def on_login(self, success, error):
if error:
self.app.set_page(
'Error',
'Failed to log in: {}'.format(str(error))
)
NotificationArea.notify('Failed to log in: {}'.format(str(error)))
return
if not success:
self.app.set_page(
'Error',
NotificationArea.notify(
'Google Play Music login failed '
'(API returned false)'
)
@ -54,20 +52,7 @@ class StartUp(urwid.Filler):
callback=self.on_login
)
else:
self.app.set_page(
'Error',
NotificationArea.notify(
'Please set your credentials on the settings page.'
)
class Error(urwid.Filler):
def __init__(self, app):
self.text = urwid.Text('Error'),
super(Error, self).__init__(
self.text,
valign='top'
)
def set_error(self, error):
self.text = 'Error:\n\n{}'.format(str(error))

View file

@ -12,7 +12,7 @@ setup(
packages=find_packages(),
entry_points={
'console_scripts': [
'clay=clay.__main__:main'
'clay=clay.app:main'
]
}
)