Skip to content

Commit a16504f

Browse files
authored
fix: font resizing (#25)
* fix: adding CSS 'breakpoints' + rescale logic for responsive appearance * fix: working on more font-resizing logic * fix: adding more rescaling logic
1 parent ead90c4 commit a16504f

File tree

13 files changed

+375
-38
lines changed

13 files changed

+375
-38
lines changed

ipod_wrapped/backend/constants.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,91 @@
3232
DEFAULT_DB_PATH = STORAGE_DIR / "ipod_wrapped.db"
3333
DEFAULT_ALBUM_ART_DIR = STORAGE_DIR / "album_art"
3434

35+
# responsive scaling - default sizes and per-tier sizes
36+
DEFAULT_SCALE_TIER = 'scale-compact'
37+
38+
DEFAULT_ALBUM_IMAGE_SIZE = 120
39+
DEFAULT_GENRE_TAG_SIZE = 50
40+
DEFAULT_GENRE_RIGHT_PANE_WIDTH = 215
41+
DEFAULT_GENRE_HEADER_IMAGE_SIZE = 70
42+
DEFAULT_GENRE_SONG_IMAGE_SIZE = 30
43+
DEFAULT_SONG_INFO_IMAGE_SIZE = 120
44+
45+
ALBUM_IMAGE_SIZES = {
46+
'scale-compact': DEFAULT_ALBUM_IMAGE_SIZE,
47+
'scale-medium': 156,
48+
'scale-large': 240,
49+
}
50+
GENRE_TAG_SIZES = {
51+
'scale-compact': DEFAULT_GENRE_TAG_SIZE,
52+
'scale-medium': 65,
53+
'scale-large': 100,
54+
}
55+
GENRE_RIGHT_PANE_WIDTHS = {
56+
'scale-compact': DEFAULT_GENRE_RIGHT_PANE_WIDTH,
57+
'scale-medium': 280,
58+
'scale-large': 430,
59+
}
60+
GENRE_HEADER_IMAGE_SIZES = {
61+
'scale-compact': DEFAULT_GENRE_HEADER_IMAGE_SIZE,
62+
'scale-medium': 91,
63+
'scale-large': 140,
64+
}
65+
GENRE_SONG_IMAGE_SIZES = {
66+
'scale-compact': DEFAULT_GENRE_SONG_IMAGE_SIZE,
67+
'scale-medium': 39,
68+
'scale-large': 60,
69+
}
70+
SONG_INFO_IMAGE_SIZES = {
71+
'scale-compact': DEFAULT_SONG_INFO_IMAGE_SIZE,
72+
'scale-medium': 156,
73+
'scale-large': 240,
74+
}
75+
DEFAULT_VISUAL_LIST_ART_SIZE = 40
76+
DEFAULT_VISUAL_LIST_ROW_HEIGHT = 50
77+
DEFAULT_VISUAL_LIST_NUM_WIDTH = 30
78+
DEFAULT_VISUAL_SUMMARY_ART_SIZE = 100
79+
80+
VISUAL_LIST_ART_SIZES = {
81+
'scale-compact': DEFAULT_VISUAL_LIST_ART_SIZE,
82+
'scale-medium': 52,
83+
'scale-large': 64,
84+
}
85+
VISUAL_LIST_ROW_HEIGHTS = {
86+
'scale-compact': DEFAULT_VISUAL_LIST_ROW_HEIGHT,
87+
'scale-medium': 65,
88+
'scale-large': 75,
89+
}
90+
VISUAL_LIST_NUM_WIDTHS = {
91+
'scale-compact': DEFAULT_VISUAL_LIST_NUM_WIDTH,
92+
'scale-medium': 39,
93+
'scale-large': 48,
94+
}
95+
VISUAL_SUMMARY_ART_SIZES = {
96+
'scale-compact': DEFAULT_VISUAL_SUMMARY_ART_SIZE,
97+
'scale-medium': 130,
98+
'scale-large': 160,
99+
}
100+
DEFAULT_VISUAL_LIST_MAX_CHARS = 25
101+
DEFAULT_VISUAL_SUMMARY_MAX_CHARS = 20
102+
DEFAULT_VISUAL_PAGE_MARGIN = 40
103+
104+
VISUAL_LIST_MAX_CHARS = {
105+
'scale-compact': DEFAULT_VISUAL_LIST_MAX_CHARS,
106+
'scale-medium': 32,
107+
'scale-large': 40,
108+
}
109+
VISUAL_SUMMARY_MAX_CHARS = {
110+
'scale-compact': DEFAULT_VISUAL_SUMMARY_MAX_CHARS,
111+
'scale-medium': 25,
112+
'scale-large': 35,
113+
}
114+
VISUAL_PAGE_MARGINS = {
115+
'scale-compact': DEFAULT_VISUAL_PAGE_MARGIN,
116+
'scale-medium': 50,
117+
'scale-large': 60,
118+
}
119+
35120
# lastfm
36121
lastfm_root = 'http://ws.audioscrobbler.com'
37122
user_auth_root = 'http://www.last.fm/api/auth'

ipod_wrapped/frontend/app.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77
import gi
88
gi.require_version('Gtk', '4.0')
99
gi.require_version('Adw', '1')
10-
from gi.repository import Gtk, Gdk, Adw
10+
from gi.repository import Gtk, Gdk, Gio, GLib, Adw
11+
12+
# responsive scaling tiers
13+
SCALE_TIERS = [
14+
('scale-compact', 0, 750),
15+
('scale-medium', 750, 1100),
16+
('scale-large', 1100, float('inf')),
17+
]
18+
BASE_WIDTH = 650
1119

1220
from .pages import AlbumsPage, SongsPage, WrappedPage, GenresPage
1321
from .widgets.bottom_bar import create_bottom_bar
@@ -121,8 +129,43 @@ def __init__(self, app, db_type="local", db_path=DEFAULT_DB_PATH, album_art_dir=
121129
# place banners (flush with bottom of window)
122130
main_box.append(self.error_banner)
123131
main_box.append(self.success_banner)
124-
125-
132+
133+
# responsive scaling
134+
self.current_tier = None
135+
self._pending_tier_update = False
136+
137+
def _get_tier(self, width: int) -> str:
138+
"""Get the scale tier name for the given width"""
139+
for name, min_w, max_w in SCALE_TIERS:
140+
if min_w <= width < max_w:
141+
return name
142+
return 'scale-compact'
143+
144+
def do_size_allocate(self, width, height, baseline):
145+
Adw.ApplicationWindow.do_size_allocate(self, width, height, baseline)
146+
tier = self._get_tier(width)
147+
if tier != self.current_tier and not self._pending_tier_update:
148+
self._pending_tier_update = True
149+
GLib.idle_add(self._apply_tier, tier)
150+
151+
def _apply_tier(self, tier) -> bool:
152+
"""Handle window resize by swapping scale tier CSS class"""
153+
self._pending_tier_update = False
154+
155+
# swap CSS class on the window
156+
for widget in (self, self.stack):
157+
if self.current_tier:
158+
widget.remove_css_class(self.current_tier)
159+
widget.add_css_class(tier)
160+
self.current_tier = tier
161+
162+
# rescale page images
163+
for page in (self.genres_page, self.albums_page, self.songs_page, self.wrapped_page):
164+
if hasattr(page, 'rescale'):
165+
page.rescale(tier)
166+
167+
return False
168+
126169
def refresh_all_pages(self) -> None:
127170
"""Refresh all pages after data has been updated"""
128171
# refresh genres page
@@ -162,7 +205,10 @@ def toggle_bottom_bar(self) -> None:
162205
class iPodWrappedApp(Adw.Application):
163206
"""GTK Application wrapper"""
164207
def __init__(self):
165-
super().__init__(application_id="com.mandycyber.iPodWrapped")
208+
super().__init__(
209+
application_id="com.mandycyber.iPodWrapped",
210+
flags=Gio.ApplicationFlags.NON_UNIQUE
211+
)
166212

167213
def do_activate(self):
168214
# force dark mode

ipod_wrapped/frontend/pages/albums_page.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from gi.repository import Gtk, GLib, Adw
55

66
from backend import grab_all_metadata, has_data
7+
from backend.constants import DEFAULT_ALBUM_IMAGE_SIZE, ALBUM_IMAGE_SIZES
78
from ..widgets.album_button import create_album_button
89

910

@@ -14,7 +15,7 @@ def __init__(self, db_type: str, db_path: str, album_art_dir: str, toggle_bottom
1415
super().__init__(orientation=Gtk.Orientation.VERTICAL)
1516

1617
# setup
17-
self.IMAGE_SIZE = 120
18+
self.IMAGE_SIZE = DEFAULT_ALBUM_IMAGE_SIZE
1819
self.toggle_bottom_bar = toggle_bottom_bar_callback
1920
self.db_type = db_type
2021
self.db_path = db_path
@@ -74,9 +75,29 @@ def _load_albums(self) -> None:
7475
self.albums = albums
7576
# populate with album buttons
7677
for album in albums:
77-
button = create_album_button(self.db_type, self.db_path, self.album_art_dir, album, self.nav_view, self.IMAGE_SIZE)
78+
button = create_album_button(self.db_type, self.db_path, self.album_art_dir, album, self.nav_view, self.IMAGE_SIZE, lambda: self.IMAGE_SIZE)
7879
self.flowbox.append(button)
7980

81+
def rescale(self, tier: str) -> None:
82+
"""Rescale album art sizes for the given tier"""
83+
self.IMAGE_SIZE = ALBUM_IMAGE_SIZES.get(tier, DEFAULT_ALBUM_IMAGE_SIZE)
84+
btn_size = self.IMAGE_SIZE + 5
85+
86+
child = self.flowbox.get_first_child()
87+
while child:
88+
# FlowBoxChild wraps the actual button
89+
button = child.get_child() if hasattr(child, 'get_child') else child
90+
button.set_size_request(btn_size, btn_size)
91+
92+
# resize the picture inside the button's box
93+
box = button.get_child()
94+
if box:
95+
pic = box.get_first_child()
96+
if pic:
97+
pic.set_size_request(self.IMAGE_SIZE, self.IMAGE_SIZE)
98+
99+
child = child.get_next_sibling()
100+
80101
def refresh(self) -> None:
81102
"""Refresh the page by reloading albums from database"""
82103
# clear existing album buttons

ipod_wrapped/frontend/pages/genres_page.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from gi.repository import Gtk, GLib
44

55
from backend import has_data, create_genre_mappings
6+
from backend.constants import (
7+
DEFAULT_GENRE_TAG_SIZE, DEFAULT_GENRE_RIGHT_PANE_WIDTH,
8+
GENRE_TAG_SIZES, GENRE_RIGHT_PANE_WIDTHS,
9+
)
610
from ..widgets.genre_tag import create_genre_tag
711

812

@@ -13,7 +17,7 @@ def __init__(self, db_type: str, db_path: str, album_art_dir: str, toggle_bottom
1317
super().__init__()
1418

1519
# setup
16-
self.TAG_SIZE = 50
20+
self.TAG_SIZE = DEFAULT_GENRE_TAG_SIZE
1721
self.toggle_bottom_bar = toggle_bottom_bar_callback
1822
self.open_start_wrapped = None
1923
self.db_type = db_type
@@ -30,7 +34,7 @@ def __init__(self, db_type: str, db_path: str, album_art_dir: str, toggle_bottom
3034
sw_right.add_css_class('genre-breakdown-scrolled')
3135

3236
self.right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
33-
self.right_box.set_size_request(215, -1)
37+
self.right_box.set_size_request(DEFAULT_GENRE_RIGHT_PANE_WIDTH, -1)
3438
self.right_box.add_css_class('genre-breakdown-pane')
3539
sw_right.set_child(self.right_box)
3640

@@ -93,6 +97,22 @@ def set_start_wrapped_callback(self, callback) -> None:
9397
self.open_start_wrapped = callback
9498
self._load_genre_tags()
9599

100+
def rescale(self, tier: str) -> None:
101+
"""Rescale genre tag sizes and right pane width for the given tier"""
102+
self.TAG_SIZE = GENRE_TAG_SIZES.get(tier, DEFAULT_GENRE_TAG_SIZE)
103+
tag_btn_size = self.TAG_SIZE + 5
104+
105+
# resize existing tags
106+
child = self.flowbox.get_first_child()
107+
while child:
108+
button = child.get_child() if hasattr(child, 'get_child') else child
109+
button.set_size_request(tag_btn_size, tag_btn_size)
110+
child = child.get_next_sibling()
111+
112+
# resize right pane
113+
pane_width = GENRE_RIGHT_PANE_WIDTHS.get(tier, DEFAULT_GENRE_RIGHT_PANE_WIDTH)
114+
self.right_box.set_size_request(pane_width, -1)
115+
96116
def refresh(self) -> None:
97117
"""Refresh the page by reloading genres from database"""
98118
# clear existing genre tags

ipod_wrapped/frontend/pages/songs_page.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from gi.repository import Gtk, GLib, Gio
44

55
from backend import has_data, grab_all_songs, ms_to_mmss
6+
from backend.constants import DEFAULT_SONG_INFO_IMAGE_SIZE, SONG_INFO_IMAGE_SIZES
67
from ..widgets.songs_table import create_song_store, create_song_selection_model, create_songs_table, Song
78
from ..widgets.song_info import display_song_info
89

@@ -20,6 +21,7 @@ def __init__(self, db_type: str, db_path: str, album_art_dir: str, toggle_bottom
2021
self.db_path = db_path
2122
self.album_art_dir = album_art_dir
2223
self.selected_song_index = None
24+
self.song_image_size = DEFAULT_SONG_INFO_IMAGE_SIZE
2325

2426
self.add_css_class('page-area')
2527
self.add_css_class('songs-page')
@@ -126,7 +128,7 @@ def _update_song_display(self) -> None:
126128
child = self.song_info_box.get_first_child()
127129

128130
# rebuild
129-
song_info_widget = display_song_info(song_data)
131+
song_info_widget = display_song_info(song_data, image_size=self.song_image_size)
130132
self.song_info_box.append(song_info_widget)
131133
self.song_info_box.set_visible(True)
132134

@@ -140,6 +142,13 @@ def _scroll_to_top(self) -> None:
140142
if vadj:
141143
vadj.set_value(0)
142144

145+
def rescale(self, tier: str) -> None:
146+
"""Rescale image sizes for the given tier"""
147+
self.song_image_size = SONG_INFO_IMAGE_SIZES.get(tier, DEFAULT_SONG_INFO_IMAGE_SIZE)
148+
# re-render current song info display if visible
149+
if self.song_info_box.get_visible():
150+
self._update_song_display()
151+
143152
def refresh(self) -> None:
144153
"""Refresh the page by reloading songs from database"""
145154
self.store.remove_all()

ipod_wrapped/frontend/pages/wrapped_page.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
gi.require_version('Gtk', '4.0')
33
from gi.repository import Gtk
44

5+
from backend.constants import DEFAULT_SCALE_TIER
56
from ..widgets.stats_filters import StatsFilters
67

78

@@ -16,6 +17,7 @@ def __init__(self, db_type: str, db_path: str, album_art_dir: str, toggle_bottom
1617
self.db_type = db_type
1718
self.db_path = db_path
1819
self.album_art_dir = album_art_dir
20+
self.tier = DEFAULT_SCALE_TIER
1921
self.stats_filter = StatsFilters()
2022

2123
self.add_css_class('page-area')
@@ -56,6 +58,11 @@ def __init__(self, db_type: str, db_path: str, album_art_dir: str, toggle_bottom
5658
self.set_child(paned)
5759

5860

61+
def rescale(self, tier: str) -> None:
62+
"""Store the current tier for when stats visuals are generated"""
63+
self.tier = tier
64+
self.stats_filter.tier = tier
65+
5966
def refresh(self) -> None:
6067
"""Refresh the page by reloading data from database"""
6168
pass

ipod_wrapped/frontend/widgets/album_button.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
from gi.repository import Gtk, Gdk, Gio, Pango, GdkPixbuf, Adw, GLib
88

99
from backend import grab_all_songs, ms_to_mmss
10+
from backend.constants import DEFAULT_SONG_INFO_IMAGE_SIZE
1011
from .songs_table import create_song_store, create_song_selection_model, create_songs_table, Song
1112
from .song_info import display_song_info
1213

13-
def create_album_button(db_type: str, db_path: str, album_art_dir: str, album_info: dict, nav_view: Adw.NavigationView, image_size: int = 120) -> Gtk.Button:
14+
def create_album_button(db_type: str, db_path: str, album_art_dir: str, album_info: dict, nav_view: Adw.NavigationView, image_size: int = 120, get_song_image_size=None) -> Gtk.Button:
1415
"""Creates a button with Album Art, Name, and Artist.
1516
1617
Args:
@@ -20,6 +21,7 @@ def create_album_button(db_type: str, db_path: str, album_art_dir: str, album_in
2021
album_info (dict): Album metadata containing art, name, artist, genres, songs
2122
nav_view (Adw.NavigationView): Navigation view to push detail page onto
2223
image_size (int): Size of the album art image
24+
get_song_image_size (callable): Returns current scaled image size for song info display
2325
2426
Returns:
2527
Gtk.Button: The button with album art and info
@@ -61,10 +63,10 @@ def create_album_button(db_type: str, db_path: str, album_art_dir: str, album_in
6163
box.append(artist_label)
6264

6365
button.set_child(box)
64-
button.connect("clicked", lambda btn: _show_album_info(album_info, db_type, db_path, album_art_dir, nav_view))
66+
button.connect("clicked", lambda btn: _show_album_info(album_info, db_type, db_path, album_art_dir, nav_view, get_song_image_size))
6567
return button
6668

67-
def _show_album_info(album_info: dict, db_type: str, db_path: str, album_art_dir: str, nav_view: Adw.NavigationView) -> None:
69+
def _show_album_info(album_info: dict, db_type: str, db_path: str, album_art_dir: str, nav_view: Adw.NavigationView, get_song_image_size=None) -> None:
6870
"""Shows the given album info when an album cover is clicked
6971
Absolutely insanity how chunky this function got. TODO: come back and make better
7072
"""
@@ -142,7 +144,8 @@ def on_selection_changed(sel, position, n_items):
142144
child = song_info_box.get_first_child()
143145

144146
# rebuild
145-
song_info_widget = display_song_info(song_data)
147+
img_size = get_song_image_size() if get_song_image_size else DEFAULT_SONG_INFO_IMAGE_SIZE
148+
song_info_widget = display_song_info(song_data, image_size=img_size)
146149
song_info_box.append(song_info_widget)
147150
song_info_box.set_visible(True)
148151

@@ -153,7 +156,8 @@ def on_selection_changed(sel, position, n_items):
153156
def select_first():
154157
selection.set_selected(0)
155158
# manually trigger display
156-
song_info_widget = display_song_info(songs_data[0])
159+
img_size = get_song_image_size() if get_song_image_size else DEFAULT_SONG_INFO_IMAGE_SIZE
160+
song_info_widget = display_song_info(songs_data[0], image_size=img_size)
157161
song_info_box.append(song_info_widget)
158162
song_info_box.set_visible(True)
159163
return False

0 commit comments

Comments
 (0)