优化UI组件
parent
942f16cdeb
commit
1a4cb930d1
34
main.py
34
main.py
|
@ -8,38 +8,18 @@ from pprint import pprint as print
|
|||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
|
||||
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
from utils import run_async
|
||||
|
||||
from window import SonistWindow
|
||||
from about_app import AboutWindow
|
||||
|
||||
from mpd import MPDClient
|
||||
|
||||
app_id = 'fun.wkit.sonist'
|
||||
home_dir = os.getenv('HOME')
|
||||
|
||||
|
||||
# 定义一个异步修饰器, 用于在子线程中运行一些会阻塞主线程的任务
|
||||
def run_async(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
return thread
|
||||
return wrapper
|
||||
|
||||
# 类型js的settimeout的修饰器
|
||||
def set_timeout(timeout = 0.5):
|
||||
def decorator(callback):
|
||||
def wrapper(*args):
|
||||
t = threading.Timer(timeout, callback, args=args)
|
||||
t.start()
|
||||
return t
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
|
||||
def get_music_dir():
|
||||
with open(f'{home_dir}/.mpd/mpd.conf', 'r') as f:
|
||||
|
@ -60,8 +40,13 @@ class Application(Gtk.Application):
|
|||
self.timer = None
|
||||
|
||||
self.music_dir = get_music_dir()
|
||||
self.album_cache_dir = f"{home_dir}/.cache/sonist/album"
|
||||
self.lyric_cache_dir = f"{home_dir}/.cache/sonist/lyric"
|
||||
self.mpd = MPDClient()
|
||||
|
||||
os.makedirs(self.album_cache_dir, exist_ok = True)
|
||||
os.makedirs(self.lyric_cache_dir, exist_ok = True)
|
||||
|
||||
self.connect('window-removed', self.on_window_removed)
|
||||
|
||||
|
||||
|
@ -82,7 +67,6 @@ class Application(Gtk.Application):
|
|||
self.about = AboutWindow()
|
||||
self.add_window(self.window)
|
||||
self.window.show_all()
|
||||
# self.about.show_all()
|
||||
|
||||
self.connect_mpd()
|
||||
|
||||
|
@ -95,6 +79,8 @@ class Application(Gtk.Application):
|
|||
print('朕要休息了~~~')
|
||||
|
||||
|
||||
def quit_all(self):
|
||||
self.remove_window(self.window)
|
||||
|
||||
""" class ApplicationService(dbus.service.Object):
|
||||
def __init__(self, app):
|
||||
|
|
|
@ -64,7 +64,7 @@ class CtrlBox(Gtk.Box):
|
|||
self.emit('clicked', 'next_btn')
|
||||
|
||||
elif btn == self.vol_btn:
|
||||
self.emit('clicked','vol_btn')
|
||||
self.emit('clicked', 'vol_btn')
|
||||
|
||||
|
||||
def toggle_play_btn(self, on = True):
|
||||
|
|
|
@ -7,6 +7,10 @@ from gi.repository import Gtk, Gdk, GObject
|
|||
from .image import ScaleImage
|
||||
|
||||
class OptionMenu(Gtk.Menu):
|
||||
|
||||
app = None
|
||||
on_top = False
|
||||
|
||||
def __init__(self, app):
|
||||
Gtk.Menu.__init__(self)
|
||||
self.app = app
|
||||
|
@ -46,13 +50,20 @@ class OptionMenu(Gtk.Menu):
|
|||
match(item.name):
|
||||
case '首选项':
|
||||
pass
|
||||
|
||||
case '窗口置顶':
|
||||
pass
|
||||
self.on_top = not self.on_top
|
||||
self.app.window.set_keep_above(self.on_top)
|
||||
if self.on_top:
|
||||
item.select()
|
||||
else:
|
||||
item.deselect()
|
||||
|
||||
case '退出应用':
|
||||
pass
|
||||
self.app.quit_all()
|
||||
|
||||
case '关于播放器':
|
||||
self.app.about.present()
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
|
21
ui/text.py
21
ui/text.py
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TextBox(Gtk.Box):
|
||||
def __init__(self, width, height):
|
||||
Gtk.Box.__init__(self)
|
||||
|
||||
self.set_size_request(width, height)
|
||||
|
||||
self.label = Gtk.Label()
|
||||
self.add(self.label)
|
||||
|
||||
|
||||
|
||||
def set_text(self, string):
|
||||
self.label.set_text('孙晓 - 丹歌惊鸿')
|
||||
return self
|
|
@ -36,9 +36,11 @@ class Timebar(Gtk.Fixed):
|
|||
self.duration = Gtk.Label()
|
||||
|
||||
self.curr.set_name('text')
|
||||
self.curr.set_selectable(False)
|
||||
self.duration.set_name('text')
|
||||
self.duration.set_selectable(False)
|
||||
self.duration.set_justify(Gtk.Justification.RIGHT)
|
||||
self.duration.set_xalign(1)
|
||||
self.duration.set_name('text')
|
||||
|
||||
self.slider.connect('change-value', self.debounce)
|
||||
self.slider.set_sensitive(False)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class TitleText(Gtk.Label):
|
||||
def __init__(self, text = 'mpd loading...'):
|
||||
Gtk.Box.__init__(self)
|
||||
|
||||
self.set_size_request(256, 20)
|
||||
self.set_name('text')
|
||||
self.set_hexpand(True)
|
||||
self.set_hexpand_set(True)
|
||||
self.set_halign(Gtk.Align.CENTER)
|
||||
self.set_text(text)
|
||||
|
||||
|
||||
def update(self, text):
|
||||
self.set_text(text)
|
||||
return self
|
48
utils.py
48
utils.py
|
@ -1,10 +1,14 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
import gi, io, base64
|
||||
from gi.repository import Gdk, GLib, GdkPixbuf, Gio
|
||||
import gi, io, base64, threading
|
||||
from gi.repository import Gdk, GLib, GdkPixbuf, Gio, GObject
|
||||
from PIL import Image, ImageFilter
|
||||
|
||||
|
||||
empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
|
||||
|
||||
|
||||
def pixbuf_to_pil(pixbuf):
|
||||
data = pixbuf.get_pixels()
|
||||
w = pixbuf.get_width()
|
||||
|
@ -23,18 +27,28 @@ def pil_to_pixbuf(img):
|
|||
alpha = img.mode == 'RGBA'
|
||||
w, h = img.size
|
||||
rowstride = w * 4
|
||||
try:
|
||||
return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride)
|
||||
except:
|
||||
return empty_pixbuf
|
||||
|
||||
|
||||
def pic_to_pixbuf(pic):
|
||||
data = pic.data
|
||||
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
|
||||
try:
|
||||
return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
|
||||
except:
|
||||
return empty_pixbuf
|
||||
|
||||
def base64_to_pixbuf(base64_str):
|
||||
data = base64.b64decode(base64_str)
|
||||
input_stream = Gio.MemoryInputStream.new_from_data(data, None)
|
||||
try:
|
||||
return GdkPixbuf.Pixbuf.new_from_stream(input_stream, None)
|
||||
except:
|
||||
return empty_pixbuf
|
||||
|
||||
|
||||
def blur_image(pixbuf):
|
||||
# 加载图片并确认该图片为RGBA模式,保证透明度
|
||||
|
@ -44,3 +58,33 @@ def blur_image(pixbuf):
|
|||
img.alpha_composite(mask)
|
||||
|
||||
return pil_to_pixbuf(img)
|
||||
|
||||
|
||||
|
||||
# 定义一个异步修饰器, 用于在子线程中运行一些会阻塞主线程的任务
|
||||
def run_async(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
return thread
|
||||
return wrapper
|
||||
|
||||
# 类型js的settimeout的修饰器
|
||||
def set_timeout(timeout = 0.5):
|
||||
def decorator(callback):
|
||||
def wrapper(*args):
|
||||
t = threading.Timer(timeout, callback, args=args)
|
||||
t.start()
|
||||
return t
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# 定义一个修饰器, 用于将当前方法转到主线程中运行 (子线程中调用方法时)
|
||||
def idle(func):
|
||||
def wrapper(*args):
|
||||
GObject.idle_add(func, *args)
|
||||
return wrapper
|
||||
|
||||
|
|
50
window.py
50
window.py
|
@ -1,28 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import gi, sys, os, mutagen, threading
|
||||
# from pprint import pprint as print
|
||||
import gi, sys, os, mutagen, base64
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
|
||||
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf, GObject
|
||||
|
||||
from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf
|
||||
from utils import blur_image, pic_to_pixbuf, base64_to_pixbuf, idle
|
||||
|
||||
from ui.image import ScaleImage
|
||||
from ui.slider import Slider
|
||||
from ui.image_button import ImageButton
|
||||
from ui.text import TextBox
|
||||
from ui.title_text import TitleText
|
||||
from ui.ctrl_box import CtrlBox
|
||||
from ui.timebar import Timebar
|
||||
from ui.option_menu import OptionMenu
|
||||
|
||||
|
||||
# 定义一个修饰器, 用于将当前方法转到主线程中运行 (子线程中调用方法时)
|
||||
def idle(func):
|
||||
def wrapper(*args):
|
||||
GObject.idle_add(func, *args)
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
class SonistWindow(Gtk.Window):
|
||||
|
@ -36,7 +30,7 @@ class SonistWindow(Gtk.Window):
|
|||
self.app = app
|
||||
self.mpd = app.mpd
|
||||
|
||||
self.connect("destroy", self.quited)
|
||||
self.connect("destroy", lambda win: app.remove_window(win))
|
||||
|
||||
self.mpd.connect('offline', lambda o: self.reset_player())
|
||||
self.mpd.connect('online', lambda o: self.sync_state(None, None, True))
|
||||
|
@ -88,10 +82,7 @@ class SonistWindow(Gtk.Window):
|
|||
|
||||
# title
|
||||
|
||||
# self.title_box = TextBox(256, 20)
|
||||
self.title_box = Gtk.Label()
|
||||
self.title_box.set_name('text')
|
||||
self.title_box.set_text('mpd loading...')
|
||||
self.title_box = TitleText()
|
||||
|
||||
layout.put(self.title_box, 27, 244)
|
||||
|
||||
|
@ -99,7 +90,7 @@ class SonistWindow(Gtk.Window):
|
|||
# 播放进度
|
||||
self.timebar = Timebar()
|
||||
self.timebar.connect('seeked', lambda a,v: self.mpd.seekcur(v))
|
||||
layout.put(self.timebar, 24, 270)
|
||||
layout.put(self.timebar, 24, 276)
|
||||
|
||||
|
||||
# 控制条
|
||||
|
@ -245,43 +236,46 @@ class SonistWindow(Gtk.Window):
|
|||
if played != 'stop':
|
||||
song = song or self.mpd.currentsong()
|
||||
# 更新歌曲信息
|
||||
self.title_box.set_text(f"{song.get('artist')} - {song.get('title')}")
|
||||
self.title_box.update(f"{song.get('artist')} - {song.get('title')}")
|
||||
self.update_playtime(self.stat)
|
||||
|
||||
filepath = f"./album/{song['title']}.png"
|
||||
songpath = f"{self.app.music_dir}/{song['file']}"
|
||||
title = song['file']
|
||||
title_hex = base64.b64encode(title.encode()).hex()
|
||||
|
||||
filepath = f"{self.app.album_cache_dir}/{title_hex}.png"
|
||||
songpath = f"{self.app.music_dir}/{title}"
|
||||
|
||||
if os.path.isfile(filepath):
|
||||
self.update_album(filepath)
|
||||
else:
|
||||
|
||||
id3 = mutagen.File(songpath)
|
||||
|
||||
pic = None
|
||||
try:
|
||||
if id3.tags.get('APIC:'):
|
||||
pic = id3.tags['APIC:']
|
||||
elif len(id3.pictures) > 0:
|
||||
pic = id3.pictures[0]
|
||||
|
||||
if pic is not None:
|
||||
if pic is None:
|
||||
self.update_album()
|
||||
else:
|
||||
album = pic_to_pixbuf(pic)
|
||||
self.update_album(album)
|
||||
except:
|
||||
|
||||
except Exception as err:
|
||||
pass
|
||||
|
||||
@idle
|
||||
def reset_player(self):
|
||||
self.ctrl_box.disabled = True
|
||||
self.title_box.set_text('mpd is offline...')
|
||||
self.title_box.update('mpd is offline...')
|
||||
self.timebar.update_time()
|
||||
self.update_album('./usr/share/sonist/avatar.jpg')
|
||||
self.update_album()
|
||||
|
||||
@idle
|
||||
def update_album(self, filepath):
|
||||
def update_album(self, filepath = './usr/share/sonist/avatar.jpg'):
|
||||
self.set_background_image(filepath)
|
||||
self.album.reset(filepath).set_radius(64)
|
||||
|
||||
|
||||
def quited(self, win):
|
||||
self.app.remove_window(self)
|
||||
|
||||
|
|
Loading…
Reference in New Issue