1# Copyright 2021 The GNOME Music developers 2# 3# GNOME Music is free software; you can redistribute it and/or modify 4# it under the terms of the GNU General Public License as published by 5# the Free Software Foundation; either version 2 of the License, or 6# (at your option) any later version. 7# 8# GNOME Music is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License along 14# with GNOME Music; if not, write to the Free Software Foundation, Inc., 15# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 16# 17# The GNOME Music authors hereby grant permission for non-GPL compatible 18# GStreamer plugins to be used and distributed together with GStreamer 19# and GNOME Music. This permission is above and beyond the permissions 20# granted by the GPL license by which GNOME Music is covered. If you 21# modify this code, you may extend this exception to your version of the 22# code, but you are not obligated to do so. If you do not wish to do so, 23# delete this exception statement from your version. 24 25from enum import Enum 26from math import pi 27from typing import Dict, Tuple 28 29import cairo 30from gi.repository import Gtk, GObject 31 32from gnomemusic.utils import ArtSize 33 34 35def make_icon_frame( 36 icon_surface, art_size=None, scale=1, default_icon=False, 37 round_shape=False): 38 """Create an Art frame, square or round. 39 40 :param cairo.Surface icon_surface: The surface to use 41 :param art_size: The size of the art 42 :param int scale: The scale of the art 43 :param bool default_icon: Indicates of this is a default icon 44 :param bool round_shape: Square or round indicator 45 46 :return: The framed surface 47 :rtype: cairo.Surface 48 """ 49 degrees = pi / 180 50 if art_size == ArtSize.SMALL: 51 radius = 4.5 52 else: 53 radius = 9 54 icon_w = icon_surface.get_width() 55 icon_h = icon_surface.get_height() 56 ratio = icon_h / icon_w 57 58 # Scale down the image according to the biggest axis 59 if ratio > 1: 60 w = int(art_size.width / ratio) 61 h = art_size.height 62 else: 63 w = art_size.width 64 h = int(art_size.height * ratio) 65 66 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w * scale, h * scale) 67 surface.set_device_scale(scale, scale) 68 ctx = cairo.Context(surface) 69 70 if round_shape: 71 ctx.arc(w / 2, h / 2, w / 2, 0, 2 * pi) 72 else: 73 ctx.arc(w - radius, radius, radius, -90 * degrees, 0 * degrees) 74 ctx.arc( 75 w - radius, h - radius, radius, 0 * degrees, 90 * degrees) 76 ctx.arc(radius, h - radius, radius, 90 * degrees, 180 * degrees) 77 ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees) 78 79 if default_icon: 80 ctx.set_source_rgb(1, 1, 1) 81 ctx.fill() 82 ctx.set_source_rgba(0, 0, 0, 0.3) 83 ctx.mask_surface(icon_surface, w / 3, h / 3) 84 ctx.fill() 85 else: 86 ctx.scale((w * scale) / icon_w, (h * scale) / icon_h) 87 ctx.set_source_surface(icon_surface, 0, 0) 88 ctx.fill() 89 90 return surface 91 92 93class DefaultIcon(GObject.GObject): 94 """Provides the symbolic fallback icons.""" 95 96 class Type(Enum): 97 ALBUM = "folder-music-symbolic" 98 ARTIST = "avatar-default-symbolic" 99 100 _cache: Dict[ 101 Tuple["DefaultIcon.Type", ArtSize, int, bool], cairo.Surface] = {} 102 103 _default_theme = Gtk.IconTheme.get_default() 104 105 def __init__(self): 106 super().__init__() 107 108 def _make_default_icon(self, icon_type, art_size, scale, round_shape): 109 icon_info = self._default_theme.lookup_icon_for_scale( 110 icon_type.value, art_size.width / 3, scale, 0) 111 icon = icon_info.load_surface() 112 113 icon_surface = make_icon_frame( 114 icon, art_size, scale, True, round_shape=round_shape) 115 116 return icon_surface 117 118 def get(self, icon_type, art_size, scale=1): 119 """Returns the requested symbolic icon 120 121 Returns a cairo surface of the requested symbolic icon in the 122 given size and shape. 123 124 :param enum icon_type: The DefaultIcon.Type of the icon 125 :param enum art_size: The ArtSize requested 126 :param int scale: The scale 127 128 :return: The symbolic icon 129 :rtype: cairo.Surface 130 """ 131 if icon_type == DefaultIcon.Type.ALBUM: 132 round_shape = False 133 else: 134 round_shape = True 135 136 if (icon_type, art_size, scale, round_shape) not in self._cache.keys(): 137 new_icon = self._make_default_icon( 138 icon_type, art_size, scale, round_shape) 139 self._cache[(icon_type, art_size, scale, round_shape)] = new_icon 140 141 return self._cache[(icon_type, art_size, scale, round_shape)] 142