Skip to content

Commit 1fe9335

Browse files
committed
feat(sample): use JetBrains Mono font in code examples
Load JetBrains Mono Regular and Bold from fonts directory and apply it to CodeBlock instead of FontFamily.Monospace. Code examples now render with the developer-friendly JetBrains Mono font.
1 parent 200fcc9 commit 1fe9335

5 files changed

Lines changed: 211 additions & 10 deletions

File tree

Binary file not shown.
Binary file not shown.

sample/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/sample/gallery/CodeBlock.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import kotlinx.coroutines.delay
5050
@Suppress("DEPRECATION")
5151
@Composable
5252
fun CodeBlock(code: String) {
53+
val jetBrainsMono = JetBrainsMonoFontFamily()
5354
val clipboardManager = LocalClipboardManager.current
5455
var copied by remember { mutableStateOf(false) }
5556
val isDark = MacosTheme.colorScheme.isDark
@@ -174,7 +175,7 @@ fun CodeBlock(code: String) {
174175
text = highlightedCode,
175176
style =
176177
TextStyle(
177-
fontFamily = FontFamily.Monospace,
178+
fontFamily = jetBrainsMono,
178179
fontSize = MacosTheme.typography.caption1.fontSize,
179180
color = MacosTheme.colorScheme.textSecondary,
180181
),
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.github.kdroidfilter.nucleus.ui.apple.macos.sample.gallery
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.text.font.FontFamily
5+
import androidx.compose.ui.text.font.FontWeight
6+
import composemacosui.sample.generated.resources.Res
7+
import composemacosui.sample.generated.resources.jetbrains_mono_bold
8+
import composemacosui.sample.generated.resources.jetbrains_mono_regular
9+
import org.jetbrains.compose.resources.Font
10+
11+
@Composable
12+
fun JetBrainsMonoFontFamily(): FontFamily =
13+
FontFamily(
14+
Font(Res.font.jetbrains_mono_regular, weight = FontWeight.Normal),
15+
Font(Res.font.jetbrains_mono_bold, weight = FontWeight.Bold),
16+
)

sample/src/webMain/resources/index.html

Lines changed: 193 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,200 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<title>Compose Mac OS 26 UI</title>
77
<link type="text/css" rel="stylesheet" href="styles.css">
8+
<style>
9+
#boot-screen {
10+
position: fixed;
11+
inset: 0;
12+
background: #000;
13+
display: flex;
14+
align-items: flex-end;
15+
justify-content: center;
16+
padding-bottom: 80px;
17+
z-index: 9999;
18+
opacity: 1;
19+
transition: opacity 0.7s ease;
20+
}
21+
#boot-screen.fade-out {
22+
opacity: 0;
23+
pointer-events: none;
24+
}
25+
.boot-progress-wrap {
26+
width: 180px;
27+
height: 4px;
28+
background: rgba(255, 255, 255, 0.15);
29+
border-radius: 2px;
30+
overflow: hidden;
31+
}
32+
.boot-progress-bar {
33+
height: 100%;
34+
background: #fff;
35+
border-radius: 2px;
36+
width: 0%;
37+
transition: width 0.2s ease-out;
38+
}
39+
</style>
840
</head>
9-
<body style="text-align: center; align-content: center">
10-
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 50 50" role="presentation">
11-
<circle cx="25" cy="25" r="20" stroke="#ccc" stroke-width="4" fill="none"/>
12-
<circle cx="25" cy="25" r="20" stroke="#333" stroke-width="4" fill="none" stroke-linecap="round"
13-
stroke-dasharray="90 125">
14-
<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s"
15-
repeatCount="indefinite"/>
16-
</circle>
17-
</svg>
41+
<body>
42+
<div id="boot-screen">
43+
<div class="boot-progress-wrap">
44+
<div class="boot-progress-bar" id="boot-bar"></div>
45+
</div>
46+
</div>
47+
48+
<script>(function () {
49+
'use strict';
50+
51+
var bar = document.getElementById('boot-bar');
52+
var screen = document.getElementById('boot-screen');
53+
var wasmProgress = {}; // url -> { loaded, total }
54+
var dismissed = false;
55+
var barPct = 0;
56+
57+
function setBar(pct) {
58+
barPct = Math.max(barPct, Math.min(100, pct));
59+
bar.style.width = barPct + '%';
60+
}
61+
62+
function dismiss() {
63+
if (dismissed) return;
64+
dismissed = true;
65+
screen.classList.add('fade-out');
66+
setTimeout(function () { if (screen.parentNode) screen.parentNode.removeChild(screen); }, 750);
67+
}
68+
69+
// Rough expected total for Kotlin/WASM builds (2 files: ~4 MB + ~9 MB).
70+
// Used only when the server omits Content-Length.
71+
var WASM_ESTIMATED_BYTES = 14 * 1024 * 1024;
72+
73+
function recalc() {
74+
var urls = Object.keys(wasmProgress);
75+
if (urls.length === 0) return;
76+
77+
var knownTotal = 0;
78+
var loadedBytes = 0;
79+
var allDone = true;
80+
var hasTotal = true;
81+
82+
urls.forEach(function (u) {
83+
var entry = wasmProgress[u];
84+
if (!entry.total) hasTotal = false;
85+
if (!entry.done) allDone = false;
86+
knownTotal += entry.total || 0;
87+
loadedBytes += entry.loaded || 0;
88+
});
89+
90+
var pct;
91+
if (hasTotal && knownTotal > 0) {
92+
// Exact progress from Content-Length
93+
pct = (loadedBytes / knownTotal) * 100;
94+
} else {
95+
// Estimate: map received bytes against expected total, cap at 95% until done
96+
pct = Math.min(95, (loadedBytes / WASM_ESTIMATED_BYTES) * 100);
97+
}
98+
99+
setBar(pct);
100+
101+
if (allDone) {
102+
setBar(100);
103+
setTimeout(dismiss, 350);
104+
}
105+
}
106+
107+
// Patch fetch to intercept WASM downloads and track progress via ReadableStream
108+
var _fetch = window.fetch;
109+
window.fetch = function (input, init) {
110+
var url = typeof input === 'string' ? input
111+
: (input && input.url) ? input.url
112+
: String(input);
113+
114+
if (!/\.wasm(\?|$)/.test(url)) return _fetch.apply(this, arguments);
115+
116+
return _fetch.apply(this, arguments).then(function (response) {
117+
var cl = response.headers.get('Content-Length');
118+
wasmProgress[url] = { loaded: 0, total: cl ? parseInt(cl, 10) : 0 };
119+
120+
if (!response.body) {
121+
// No streaming support — mark as complete immediately
122+
wasmProgress[url].total = 1;
123+
wasmProgress[url].loaded = 1;
124+
wasmProgress[url].done = true;
125+
recalc();
126+
return response;
127+
}
128+
129+
var reader = response.body.getReader();
130+
var stream = new ReadableStream({
131+
start: function (ctrl) {
132+
(function pump() {
133+
reader.read().then(function (chunk) {
134+
if (chunk.done) {
135+
wasmProgress[url].done = true;
136+
if (!wasmProgress[url].total) {
137+
wasmProgress[url].total = wasmProgress[url].loaded;
138+
}
139+
recalc();
140+
ctrl.close();
141+
return;
142+
}
143+
wasmProgress[url].loaded += chunk.value.byteLength;
144+
recalc();
145+
ctrl.enqueue(chunk.value);
146+
pump();
147+
}).catch(function (e) { ctrl.error(e); });
148+
}());
149+
}
150+
});
151+
152+
return new Response(stream, {
153+
headers: response.headers,
154+
status: response.status,
155+
statusText: response.statusText
156+
});
157+
});
158+
};
159+
160+
// Fallback for the pure-JS build (no WASM): animate the bar with easing,
161+
// then watch for the Compose <canvas> to appear as the final signal.
162+
var fallbackHandle = null;
163+
var fallbackStarted = false;
164+
165+
function startFallback() {
166+
if (fallbackStarted) return;
167+
fallbackStarted = true;
168+
var start = Date.now();
169+
fallbackHandle = setInterval(function () {
170+
var t = Math.min(1, (Date.now() - start) / 9000);
171+
// Ease-out cubic, capped at 90% until canvas appears
172+
setBar((1 - Math.pow(1 - t, 3)) * 90);
173+
}, 60);
174+
}
175+
176+
// Watch for the Compose canvas — signals that the app has rendered
177+
var canvasMo = new MutationObserver(function () {
178+
if (!document.querySelector('canvas')) return;
179+
canvasMo.disconnect();
180+
clearInterval(fallbackHandle);
181+
if (!dismissed) {
182+
setBar(100);
183+
setTimeout(dismiss, 350);
184+
}
185+
});
186+
canvasMo.observe(document.body, { childList: true, subtree: true });
187+
188+
// If no WASM fetch is detected within 1 s, assume JS-only target
189+
setTimeout(function () {
190+
if (Object.keys(wasmProgress).length === 0) startFallback();
191+
}, 1000);
192+
193+
// Absolute safety net — never leave the boot screen up forever
194+
setTimeout(function () {
195+
clearInterval(fallbackHandle);
196+
setBar(100);
197+
setTimeout(dismiss, 350);
198+
}, 45000);
199+
}());
200+
</script>
201+
18202
<script type="application/javascript" src="sample.js"></script>
19203
</body>
20204
</html>

0 commit comments

Comments
 (0)