diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..fd60e28 --- /dev/null +++ b/Readme.md @@ -0,0 +1,5 @@ +
+ +
+ + diff --git a/main.py b/main.py index 2156770..54143b4 100755 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GLib, GdkPixbuf from window import SonistWindow + # from mpd.asyncio import MPDClient from mpd.base import MPDClient @@ -20,7 +21,7 @@ class Application(Gtk.Application): def __init__(self): Gtk.Application.__init__(self, application_id = app_id) - self.window = SonistWindow() + self.add_window(SonistWindow()) self.mpc = MPDClient() self.mpc.timeout = 10 @@ -29,6 +30,8 @@ class Application(Gtk.Application): def do_activate(self): print('hello mpc') + self.set_app_menu(None) + self.set_menubar(None) # self.window.show_all() diff --git a/preview/image.png b/preview/image.png new file mode 100644 index 0000000..da35d02 Binary files /dev/null and b/preview/image.png differ diff --git a/ui/image.py b/ui/image.py new file mode 100644 index 0000000..051ebe0 --- /dev/null +++ b/ui/image.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +import gi, cairo, math +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk, GdkPixbuf + + +class ScaleImage(Gtk.Image): + def __init__(self, filepath): + Gtk.Image.__init__(self) + + self.origin = GdkPixbuf.Pixbuf.new_from_file(filepath) + self.pixbuf = self.origin + + self.width = self.origin.get_width() + self.height = self.origin.get_height() + + self.set_from_pixbuf(self.origin) + + + + def resize(self, width, height): + self.width = width + self.height = height + self.pixbuf = self.origin.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR) + self.set_from_pixbuf(self.pixbuf) + + + def set_radius(self, radius = 0): + w = self.width + h = self.height + surface = cairo.ImageSurface(cairo.Format.ARGB32, w, h) + ctx = cairo.Context(surface) + Gdk.cairo_set_source_pixbuf(ctx, self.pixbuf, 0, 0) + + # 左上角 圆角 + ctx.arc(radius, radius, radius, -math.pi, -math.pi / 2.) + ctx.line_to(w - radius, 0) + + # 右上角 圆角 + ctx.arc(w - radius, radius, radius, -math.pi / 2., 0) + ctx.line_to(w, -radius) + + # 右下角 圆角 + ctx.arc(w - radius, h - radius, radius, 0, math.pi / 2.) + ctx.line_to(radius, h) + + # 左下角 圆角 + ctx.arc(radius, h - radius, radius, math.pi / 2., math.pi) + ctx.close_path() + + ctx.clip() + ctx.paint() + + pixbuf = Gdk.pixbuf_get_from_surface(surface, 0, 0, w, h) + self.set_from_pixbuf(pixbuf) + + diff --git a/ui/image_button.py b/ui/image_button.py new file mode 100644 index 0000000..7645b05 --- /dev/null +++ b/ui/image_button.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +from .image import ScaleImage + +class ImageButton(Gtk.Button): + def __init__(self, filepath, width = 26, height = 26): + Gtk.Button.__init__(self) + + self.set_name('ImageButton') + self.set_size_request(width, height) + + image = ScaleImage(filepath) + image.resize(width, height) + + self.set_image(image) + + css_provider = Gtk.CssProvider() + style = f""" + #ImageButton {{ + border: 0; + background-color: transparent; + outline: none; + }} + """ + css_provider.load_from_data(style.encode('UTF-8')) + + context = self.get_style_context() + context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) diff --git a/ui/slider.py b/ui/slider.py new file mode 100644 index 0000000..75179d9 --- /dev/null +++ b/ui/slider.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +class Slider(Gtk.Scale): + def __init__(self, width = 256, height = 5): + Gtk.Scale.__init__(self) + + self.set_name('Slider') + self.set_range(0, 100) + self.set_size_request(width, height) + self.set_draw_value(False) + + + + css_provider = Gtk.CssProvider() + style = f""" + #Slider {{ + outline: none; + }} + #Slider trough {{ + background-color: rgba(129, 161, 193, 0.35); + outline: none; + }} + #Slider trough highlight {{ + background-color: rgba(163, 190, 140, 0.75); + }} + #Slider slider {{ + background-color: rgba(163, 190, 140, 0.75); + outline: none; + }} + """ + css_provider.load_from_data(style.encode('UTF-8')) + + context = self.get_style_context() + context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) diff --git a/ui/text.py b/ui/text.py new file mode 100644 index 0000000..1c861d4 --- /dev/null +++ b/ui/text.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + + +class TextBox(Gtk.EventBox): + def __init__(self, width, height): + Gtk.EventBox.__init__(self) + + self.set_size_request(width, height) + + self.label = Gtk.Label() + + align = Gtk.Alignment(xalign=0.5, yalign=0.5) + + self.add(self.label) + self.add(align) + + + def set_text(self, string): + self.label.set_text('孙晓 - 丹歌惊鸿') \ No newline at end of file diff --git a/usr/share/sonist/album.png b/usr/share/sonist/album.png new file mode 100644 index 0000000..55a7ee4 Binary files /dev/null and b/usr/share/sonist/album.png differ diff --git a/usr/share/sonist/all.png b/usr/share/sonist/all.png new file mode 100644 index 0000000..c751099 Binary files /dev/null and b/usr/share/sonist/all.png differ diff --git a/usr/share/sonist/all_a.png b/usr/share/sonist/all_a.png new file mode 100644 index 0000000..402dd6e Binary files /dev/null and b/usr/share/sonist/all_a.png differ diff --git a/usr/share/sonist/avatar.jpg b/usr/share/sonist/avatar.jpg new file mode 100644 index 0000000..fd9e49c Binary files /dev/null and b/usr/share/sonist/avatar.jpg differ diff --git a/usr/share/sonist/disk.png b/usr/share/sonist/disk.png new file mode 100644 index 0000000..c1a1d3f Binary files /dev/null and b/usr/share/sonist/disk.png differ diff --git a/usr/share/sonist/mute.png b/usr/share/sonist/mute.png new file mode 100644 index 0000000..0c9d5de Binary files /dev/null and b/usr/share/sonist/mute.png differ diff --git a/usr/share/sonist/mute_a.png b/usr/share/sonist/mute_a.png new file mode 100644 index 0000000..02630dc Binary files /dev/null and b/usr/share/sonist/mute_a.png differ diff --git a/usr/share/sonist/next.png b/usr/share/sonist/next.png new file mode 100644 index 0000000..7cb1251 Binary files /dev/null and b/usr/share/sonist/next.png differ diff --git a/usr/share/sonist/next_a.png b/usr/share/sonist/next_a.png new file mode 100644 index 0000000..2488278 Binary files /dev/null and b/usr/share/sonist/next_a.png differ diff --git a/usr/share/sonist/pause.png b/usr/share/sonist/pause.png new file mode 100644 index 0000000..c22d95b Binary files /dev/null and b/usr/share/sonist/pause.png differ diff --git a/usr/share/sonist/pause_a.png b/usr/share/sonist/pause_a.png new file mode 100644 index 0000000..5404e15 Binary files /dev/null and b/usr/share/sonist/pause_a.png differ diff --git a/usr/share/sonist/play.png b/usr/share/sonist/play.png new file mode 100644 index 0000000..7bb307d Binary files /dev/null and b/usr/share/sonist/play.png differ diff --git a/usr/share/sonist/play_a.png b/usr/share/sonist/play_a.png new file mode 100644 index 0000000..2e03b42 Binary files /dev/null and b/usr/share/sonist/play_a.png differ diff --git a/usr/share/sonist/prev.png b/usr/share/sonist/prev.png new file mode 100644 index 0000000..beeff5b Binary files /dev/null and b/usr/share/sonist/prev.png differ diff --git a/usr/share/sonist/prev_a.png b/usr/share/sonist/prev_a.png new file mode 100644 index 0000000..f23365c Binary files /dev/null and b/usr/share/sonist/prev_a.png differ diff --git a/usr/share/sonist/rand.png b/usr/share/sonist/rand.png new file mode 100644 index 0000000..46a7187 Binary files /dev/null and b/usr/share/sonist/rand.png differ diff --git a/usr/share/sonist/rand_a.png b/usr/share/sonist/rand_a.png new file mode 100644 index 0000000..6e48d1c Binary files /dev/null and b/usr/share/sonist/rand_a.png differ diff --git a/usr/share/sonist/single.png b/usr/share/sonist/single.png new file mode 100644 index 0000000..83c31ff Binary files /dev/null and b/usr/share/sonist/single.png differ diff --git a/usr/share/sonist/single_a.png b/usr/share/sonist/single_a.png new file mode 100644 index 0000000..78d6013 Binary files /dev/null and b/usr/share/sonist/single_a.png differ diff --git a/usr/share/sonist/volume.png b/usr/share/sonist/volume.png new file mode 100644 index 0000000..c77026c Binary files /dev/null and b/usr/share/sonist/volume.png differ diff --git a/usr/share/sonist/volume_a.png b/usr/share/sonist/volume_a.png new file mode 100644 index 0000000..5c18e57 Binary files /dev/null and b/usr/share/sonist/volume_a.png differ diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..6cbb619 --- /dev/null +++ b/utils.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 + + +import gi +from gi.repository import Gdk, GLib, GdkPixbuf +from PIL import Image, ImageFilter + +def pixbuf_to_pil(pixbuf): + data = pixbuf.get_pixels() + w = pixbuf.get_width() + h = pixbuf.get_height() + stride = pixbuf.get_rowstride() + mode = "RGBA" if pixbuf.get_has_alpha() else "RGB" + img = Image.frombytes(mode, (w, h), data, "raw", mode, stride) + return img + + +def pil_to_pixbuf(img): + data = [] + for p in img.getdata(): + data.extend(p) + data = bytes(data) + alpha = img.mode == 'RGBA' + w, h = img.size + rowstride = w * 4 + return GdkPixbuf.Pixbuf.new_from_bytes(GLib.Bytes.new(data), GdkPixbuf.Colorspace.RGB, alpha, 8, w, h, rowstride) + + +def blur_image(pixbuf): + # 加载图片并确认该图片为RGBA模式,保证透明度 + img = pixbuf_to_pil(pixbuf).convert('RGBA') + mask = Image.new('RGBA', img.size, (32, 32, 32,160)) + img = img.filter(ImageFilter.GaussianBlur(radius = 16)) + img.alpha_composite(mask) + + return pil_to_pixbuf(img) \ No newline at end of file diff --git a/window.py b/window.py index 3dad446..2a8989d 100644 --- a/window.py +++ b/window.py @@ -7,14 +7,113 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GLib, GdkPixbuf +from utils import blur_image +from ui.image import ScaleImage +from ui.slider import Slider +from ui.image_button import ImageButton +from ui.text import TextBox + + + + + class SonistWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self) - self.set_default_size(480, 320) + self.set_name('SonistWindow') + self.set_default_size(320, 384) + self.set_wmclass('Sonist', 'Sonist') - button = Gtk.Button() + self.set_opacity(0.9) - self.add(button) + # self.set_keep_above(True) - self.show_all() \ No newline at end of file + # album_img = './usr/share/sonist/album.png' + album_img = './usr/share/sonist/avatar.jpg' + + self.connect("destroy", self.all_quit) + + self.set_background_image(album_img) + + layout = Gtk.Layout() + + # 唱片 + disk = ScaleImage('./usr/share/sonist/disk.png') + album = ScaleImage(album_img) + + disk.resize(192, 192) + album.resize(128, 128) + album.set_radius(64) + + box = Gtk.Fixed() + box.put(disk, 0, 0) + box.put(album, 32, 32) + + layout.put(box, 64, 32) + + + # title + + title_box = TextBox(256, 20) + title_box.set_text('孙晓 - 丹歌惊鸿') + + layout.put(title_box, 32, 244) + + + # 播放进度 + slider = Slider() + layout.put(slider, 32, 270) + + + # 控制条 + ctrl_box = Gtk.Box(spacing = 6) + all = ImageButton('./usr/share/sonist/all.png') + # rand = ImageButton('./usr/share/sonist/rand.png') + prev = ImageButton('./usr/share/sonist/prev.png') + pause = ImageButton('./usr/share/sonist/pause.png', 48, 48) + next = ImageButton('./usr/share/sonist/next.png') + volume = ImageButton('./usr/share/sonist/volume.png') + + ctrl_box.pack_start(all, True, True, 0) + # ctrl_box.pack_start(rand, True, True, 0) + ctrl_box.pack_start(prev, True, True, 0) + ctrl_box.pack_start(pause, True, True, 0) + ctrl_box.pack_start(next, True, True, 0) + ctrl_box.pack_start(volume, True, True, 0) + + layout.put(ctrl_box, 48, 300) + + + + + + self.add(layout) + + self.show_all() + + + def set_background_image(self, filepath): + pixbuf = GdkPixbuf.Pixbuf.new_from_file(filepath) + pixbuf = blur_image(pixbuf) + pixbuf.savev(f"/tmp/sonist_album_cache", 'png', [], []) + + css = f""" + #SonistWindow {{ + background-image: url('/tmp/sonist_album_cache'); + background-size: 100% 100%; + background-position: center; + }} + """ + # 加载CSS样式 + css_provider = Gtk.CssProvider() + css_provider.load_from_data(css.encode('UTF-8')) + context = Gtk.StyleContext() + screen = Gdk.Screen.get_default() + context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) + + + + def all_quit(self, win): + print('朕要休息了~~~') + Gtk.main_quit() \ No newline at end of file