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