Skip to content

Commit 601f9a6

Browse files
feat: Improve font selector design + allow to select fontsize (#109)
1 parent b84dd67 commit 601f9a6

File tree

9 files changed

+184
-101
lines changed

9 files changed

+184
-101
lines changed

pretty_gpx/common/drawing/utils/drawing_figure.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ def __call__(self, paper_size: PaperSize) -> float:
3333
scale = paper_size.diag_mm/PAPER_SIZES['A4'].diag_mm
3434
return mm_to_point(self.__val_mm)*scale
3535

36+
def __mul__(self, other: float) -> 'A4Float':
37+
"""Multiply the A4Float by a scalar."""
38+
return A4Float(mm=self.__val_mm * other)
39+
40+
def __truediv__(self, other: float) -> 'A4Float':
41+
"""Divide the A4Float by a scalar."""
42+
return A4Float(mm=self.__val_mm / other)
43+
3644

3745
class MetersFloat:
3846
"""Scales a meter measurement to points based on paper size and GPX bounds."""
Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
#!/usr/bin/python3
22
"""Fonts."""
33
import os
4-
import textwrap
54
from enum import Enum
6-
from pathlib import Path
75

86
from matplotlib.font_manager import FontProperties
97

@@ -23,25 +21,3 @@ class CustomFont(Enum):
2321
def font_name(self) -> str:
2422
"""Get the font name."""
2523
return self.value.get_name()
26-
27-
def get_css_header(self) -> str | None:
28-
"""Get the CSS header for the font."""
29-
font_path = self.value.get_file()
30-
if font_path is None or not isinstance(font_path, str):
31-
return None
32-
33-
font_path = Path(font_path).name
34-
if font_path.lower().endswith('.otf'):
35-
font_format = 'opentype'
36-
elif font_path.lower().endswith('.ttf'):
37-
font_format = 'truetype'
38-
else:
39-
raise ValueError("Unsupported font format. Please provide a .otf or .ttf file.")
40-
41-
header = f'''
42-
@font-face {{
43-
font-family: '{self.font_name}';
44-
src: url('/fonts/{font_path}') format('{font_format}');
45-
}}
46-
'''
47-
return textwrap.dedent(header)

pretty_gpx/ui/pages/city/page.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def update_drawer_params(self) -> None:
6161
self.drawer.params.profile_fill_color = theme.track_color
6262
self.drawer.params.profile_font_color = theme.background_color
6363
self.drawer.params.centered_title_font_color = theme.point_color
64-
65-
self.drawer.params.centered_title_fontproperties = self.font.value.value
64+
self.drawer.params.centered_title_fontproperties = self.font.font.value
65+
self.drawer.params.centered_title_font_size = self.font._current_fontsize
6666

6767
for cat in [ScatterPointCategory.CITY_BRIDGE,
6868
ScatterPointCategory.CITY_POI_DEFAULT,

pretty_gpx/ui/pages/mountain/page.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def update_drawer_params(self) -> None:
5656
self.drawer.params.profile_fill_color = theme.track_color
5757
self.drawer.params.profile_font_color = theme.background_color
5858
self.drawer.params.centered_title_font_color = theme.peak_color
59+
self.drawer.params.centered_title_fontproperties = self.font.font.value
60+
self.drawer.params.centered_title_font_size = self.font._current_fontsize
5961

6062
for cat in [ScatterPointCategory.MOUNTAIN_PASS,
6163
ScatterPointCategory.START,

pretty_gpx/ui/pages/multi_mountain/page.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ def update_drawer_params(self) -> None:
6565
self.drawer.params.profile_fill_color = theme.track_color
6666
self.drawer.params.profile_font_color = theme.background_color
6767
self.drawer.params.centered_title_font_color = theme.peak_color
68+
self.drawer.params.centered_title_fontproperties = self.font.font.value
69+
self.drawer.params.centered_title_font_size = self.font._current_fontsize
6870

6971
for cat in [ScatterPointCategory.MOUNTAIN_HUT,
7072
ScatterPointCategory.START,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/python3
2+
"""Ui Fonts Menu, to select a font from a list and also select a font size."""
3+
from collections.abc import Awaitable
4+
from collections.abc import Callable
5+
6+
from nicegui import ui
7+
8+
from pretty_gpx.common.drawing.utils.drawing_figure import A4Float
9+
from pretty_gpx.common.drawing.utils.fonts import CustomFont
10+
from pretty_gpx.ui.pages.template.ui_font_select import UiFontSelect
11+
12+
13+
class UiFontAndSizeSelect:
14+
"""NiceGui menu to select a font in a list and also select a font size."""
15+
16+
def __init__(self, *,
17+
label: str,
18+
fonts: tuple[CustomFont, ...],
19+
on_change: Callable[[], Awaitable[None]],
20+
start_fontsize: A4Float,
21+
start_font: CustomFont | None = None,
22+
fontsize_geometric_step: float = 1.2) -> None:
23+
"""Create a UiFontAndSizeSelect."""
24+
with ui.row().classes('items-center gap-2'):
25+
font_select = UiFontSelect(label=label,
26+
fonts=fonts,
27+
on_change=on_change,
28+
start_font=start_font)
29+
30+
def on_click_minus() -> Callable[[], Awaitable[None]]:
31+
"""On click minus handler."""
32+
async def handler() -> None:
33+
self._current_fontsize /= fontsize_geometric_step
34+
await on_change()
35+
return handler
36+
37+
def on_click_plus() -> Callable[[], Awaitable[None]]:
38+
"""On click plus handler."""
39+
async def handler() -> None:
40+
self._current_fontsize *= fontsize_geometric_step
41+
await on_change()
42+
return handler
43+
44+
with ui.button(icon='remove', on_click=on_click_minus()
45+
).props('dense round').classes('bg-white text-black border border-black'):
46+
ui.tooltip('Decrease font size')
47+
with ui.button(icon='add', on_click=on_click_plus()
48+
).props('dense round').classes('bg-white text-black border border-black'):
49+
ui.tooltip('Increase font size')
50+
51+
###
52+
53+
self._font_select = font_select
54+
self._current_fontsize = start_fontsize
55+
56+
@property
57+
def font(self) -> CustomFont:
58+
"""Return the selected font."""
59+
return self._font_select.font
60+
61+
@property
62+
def fontsize(self) -> A4Float:
63+
"""Return the selected fontsize."""
64+
return self._current_fontsize
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/python3
2+
"""Ui Fonts Menu, to select a font from a list."""
3+
import os
4+
import textwrap
5+
from collections.abc import Awaitable
6+
from collections.abc import Callable
7+
8+
from nicegui import app
9+
from nicegui import ui
10+
11+
from pretty_gpx.common.drawing.utils.fonts import CustomFont
12+
from pretty_gpx.common.utils.paths import FONTS_DIR
13+
14+
app.add_static_files('/fonts', os.path.abspath(FONTS_DIR))
15+
16+
17+
class UiFontSelect:
18+
"""NiceGui menu to select a font in a list."""
19+
20+
def __init__(self,
21+
*,
22+
label: str,
23+
fonts: tuple[CustomFont, ...],
24+
on_change: Callable[[], Awaitable[None]],
25+
start_font: CustomFont | None = None) -> None:
26+
"""Create a UiFontsMenu."""
27+
if start_font is None:
28+
current_idx = 0
29+
else:
30+
current_idx = fonts.index(start_font) # Can raise ValueError if not found
31+
32+
def on_click_idx(idx: int) -> Callable[[], Awaitable[None]]:
33+
"""On click handler."""
34+
async def handler() -> None:
35+
self.change_current_idx(idx)
36+
await on_change()
37+
return handler
38+
39+
with ui.dropdown_button(label, icon="font_download", auto_close=True) as main_button:
40+
main_button.classes("bg-white text-black normal-case")
41+
42+
for font in fonts:
43+
font_css_header = get_css_header(font)
44+
if font_css_header is not None:
45+
ui.add_css(font_css_header)
46+
47+
items = [ui.item(font.font_name, on_click=on_click_idx(idx))
48+
.style(f'font-family:"{font.font_name}";')
49+
for idx, font in enumerate(fonts)]
50+
51+
###
52+
53+
self.main_button = main_button
54+
self.fonts = fonts
55+
self.items = items
56+
self.current_idx = current_idx
57+
58+
self.change_current_idx(current_idx)
59+
60+
def change_current_idx(self, new_idx: int) -> None:
61+
"""Change the current index."""
62+
if new_idx < 0 or new_idx >= len(self.fonts):
63+
raise IndexError(f"Index {new_idx} out of bounds for fonts list of length {len(self.fonts)}.")
64+
self.items[self.current_idx].classes(replace="bg-white text-black")
65+
self.items[new_idx].classes(replace="bg-primary text-white")
66+
self.current_idx = new_idx
67+
self.main_button.style(f'font-family: "{self.font.font_name}";')
68+
69+
@property
70+
def font(self) -> CustomFont:
71+
"""Return the selected font."""
72+
return self.fonts[self.current_idx]
73+
74+
75+
def get_css_header(font: CustomFont) -> str | None:
76+
"""Get the CSS header for the font."""
77+
font_path = font.value.get_file()
78+
if font_path is None or not isinstance(font_path, str):
79+
return None
80+
81+
font_path = os.path.basename(font_path)
82+
if font_path.lower().endswith('.otf'):
83+
font_format = 'opentype'
84+
elif font_path.lower().endswith('.ttf'):
85+
font_format = 'truetype'
86+
else:
87+
raise ValueError("Unsupported font format. Please provide a .otf or .ttf file.")
88+
89+
header = f'''
90+
@font-face {{
91+
font-family: '{font.font_name}';
92+
src: url('/fonts/{font_path}') format('{font_format}');
93+
}}
94+
'''
95+
return textwrap.dedent(header)

pretty_gpx/ui/pages/template/ui_fonts_menu.py

Lines changed: 0 additions & 66 deletions
This file was deleted.

pretty_gpx/ui/pages/template/ui_manager.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
from pretty_gpx.common.drawing.utils.color_theme import LightTheme
1919
from pretty_gpx.common.drawing.utils.drawer import DrawerMultiTrack
2020
from pretty_gpx.common.drawing.utils.drawer import DrawerSingleTrack
21+
from pretty_gpx.common.drawing.utils.drawing_figure import A4Float
2122
from pretty_gpx.common.drawing.utils.fonts import CustomFont
2223
from pretty_gpx.common.layout.paper_size import PAPER_SIZES
2324
from pretty_gpx.common.layout.paper_size import PaperSize
2425
from pretty_gpx.common.utils.logger import logger
2526
from pretty_gpx.common.utils.profile import profile_parallel
26-
from pretty_gpx.ui.pages.template.ui_fonts_menu import UiFontsMenu
27+
from pretty_gpx.ui.pages.template.ui_font_and_size_select import UiFontAndSizeSelect
2728
from pretty_gpx.ui.pages.template.ui_input import UiInputFloat
2829
from pretty_gpx.ui.pages.template.ui_input import UiInputStr
2930
from pretty_gpx.ui.pages.template.ui_plot import UiPlot
@@ -126,7 +127,7 @@ class UiManager(Generic[T], ABC):
126127
paper_size: UiToggle[PaperSize]
127128
title: UiInputStr
128129
dist_km: UiInputFloat
129-
font: UiFontsMenu
130+
font: UiFontAndSizeSelect
130131
dark_mode_switch: ui.switch
131132
theme: UiToggle[DarkTheme] | UiToggle[LightTheme]
132133

@@ -206,17 +207,18 @@ def __init__(self, drawer: T) -> None:
206207
#
207208

208209
with self.subclass_column:
210+
self.font = UiFontAndSizeSelect(label="Title's Font",
211+
fonts=(CustomFont.LOBSTER,
212+
CustomFont.MONOTON,
213+
CustomFont.GOCHI_HAND,
214+
CustomFont.EMILIO_20,
215+
CustomFont.ALLERTA_STENCIL),
216+
start_fontsize=A4Float(mm=20),
217+
on_change=self.on_click_update)
209218
self.title = UiInputStr.create(label='Title', value="Title", tooltip="Press Enter to update title",
210219
on_enter=self.on_click_update)
211220
self.dist_km = UiInputFloat.create(label='Distance (km)', value="", on_enter=self.on_click_update,
212221
tooltip="Press Enter to override distance from GPX")
213-
self.font = UiFontsMenu.create(fonts=(CustomFont.LOBSTER,
214-
CustomFont.MONOTON,
215-
CustomFont.GOCHI_HAND,
216-
CustomFont.EMILIO_20,
217-
CustomFont.ALLERTA_STENCIL),
218-
on_change=self.on_click_update,
219-
tooltip="Select the title's font")
220222

221223
#
222224
# New fields will be added here by the subclass

0 commit comments

Comments
 (0)