优化UI组件

master
yutent 2023-08-24 17:04:41 +08:00
parent 942f16cdeb
commit 1a4cb930d1
8 changed files with 122 additions and 84 deletions

34
main.py
View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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)

22
ui/title_text.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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)