Skip to content

Commit da7db88

Browse files
committed
feat: add Emscripten HTML template (by Nuno)
1 parent fef80ad commit da7db88

File tree

2 files changed

+323
-0
lines changed

2 files changed

+323
-0
lines changed

azul/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ if(EMSCRIPTEN)
3434
target_link_options(azul PRIVATE
3535
"SHELL:--embed-file ${CMAKE_CURRENT_SOURCE_DIR}/assets@/assets"
3636
"SHELL:--embed-file ${CUBOS_ENGINE_ASSETS_PATH}@/builtin"
37+
"SHELL:--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/emscripten.html"
3738
)
3839
target_compile_definitions(azul PRIVATE
3940
APP_ASSETS_PATH="/assets"

azul/emscripten.html

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
<!doctype html>
2+
<html lang="en-us">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
7+
<title>Cubos WebGL Player | Ondisseia</title>
8+
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
9+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols" />
10+
<style>
11+
html,
12+
body {
13+
margin: 0;
14+
padding: 0;
15+
background: black;
16+
font-family: Arial, sans-serif;
17+
color: white;
18+
overflow: hidden;
19+
height: 100%;
20+
}
21+
22+
canvas.emscripten {
23+
display: block;
24+
width: 100vw;
25+
height: 100vh;
26+
background-color: black;
27+
border: none;
28+
}
29+
30+
#status-container {
31+
position: absolute;
32+
top: 10px;
33+
left: 10px;
34+
display: flex;
35+
align-items: center;
36+
z-index: 10;
37+
font-size: 14px;
38+
color: #ccc;
39+
}
40+
41+
.spinner {
42+
width: 20px;
43+
height: 20px;
44+
border: 3px solid #f4d848;
45+
border-top: 3px solid #dc941c;
46+
border-radius: 50%;
47+
animation: spin 1s linear infinite;
48+
margin-right: 10px;
49+
}
50+
51+
@keyframes spin {
52+
0% {
53+
transform: rotate(0deg);
54+
}
55+
56+
100% {
57+
transform: rotate(360deg);
58+
}
59+
}
60+
61+
#progress {
62+
width: 100%;
63+
height: 4px;
64+
position: absolute;
65+
bottom: 0;
66+
left: 0;
67+
appearance: none;
68+
}
69+
70+
#progress::-webkit-progress-bar {
71+
background: #222;
72+
}
73+
74+
#progress::-webkit-progress-value {
75+
background: #f4d848;
76+
}
77+
78+
#fullscreen-btn {
79+
position: absolute;
80+
top: 10px;
81+
right: 10px;
82+
background: rgba(255, 255, 255, 0.1);
83+
color: white;
84+
padding: 6px 10px;
85+
border: none;
86+
border-radius: 5px;
87+
z-index: 10;
88+
cursor: pointer;
89+
}
90+
91+
#fullscreen-btn:hover {
92+
background: rgba(255, 255, 255, 0.4);
93+
}
94+
95+
#fullscreen-btn::before {
96+
content: "\26F6";
97+
/* Compass icon */
98+
font-size: clamp(14px, 3vw, 64px);
99+
}
100+
101+
#touch-controls {
102+
position: absolute;
103+
bottom: 10px;
104+
width: 100%;
105+
display: flex;
106+
justify-content: space-between;
107+
padding: 0 10px;
108+
box-sizing: border-box;
109+
pointer-events: none;
110+
}
111+
112+
#left-controls,
113+
#right-controls {
114+
display: flex;
115+
flex-direction: column;
116+
gap: 8px;
117+
pointer-events: auto;
118+
}
119+
120+
#left-controls button,
121+
#right-controls button {
122+
width: 48px;
123+
height: 48px;
124+
font-size: 18px;
125+
background: rgba(255, 255, 255, 0.1);
126+
color: white;
127+
border: none;
128+
border-radius: 6px;
129+
}
130+
131+
.dpad {
132+
display: grid;
133+
grid-template-columns: 60px 60px 60px;
134+
grid-template-rows: 60px 60px 60px;
135+
gap: 3px;
136+
justify-content: center;
137+
align-items: center;
138+
}
139+
140+
.dpad button {
141+
width: 60px;
142+
height: 60px;
143+
font-size: 20px;
144+
background: #444;
145+
color: white;
146+
border: none;
147+
border-radius: 8px;
148+
}
149+
150+
.dpad .up {
151+
grid-column: 2;
152+
grid-row: 1;
153+
}
154+
155+
.dpad .left {
156+
grid-column: 1;
157+
grid-row: 2;
158+
}
159+
160+
.dpad .right {
161+
grid-column: 3;
162+
grid-row: 2;
163+
}
164+
165+
.dpad .down {
166+
grid-column: 2;
167+
grid-row: 2;
168+
}
169+
170+
.dpad .shoot {
171+
grid-column: 2;
172+
grid-row: 4;
173+
}
174+
175+
176+
.prevent-select {
177+
-webkit-user-select: none;
178+
/* Safari */
179+
-ms-user-select: none;
180+
/* IE 10 and IE 11 */
181+
user-select: none;
182+
/* Standard syntax */
183+
}
184+
</style>
185+
</head>
186+
187+
<body>
188+
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
189+
190+
<div id="status-container">
191+
<div class="spinner" id="spinner"></div>
192+
<div id="status">Loading...</div>
193+
</div>
194+
195+
<button id="fullscreen-btn" onclick="Module.requestFullscreen(false, true)"></button>
196+
197+
<div id="touch-controls" style="visibility: hidden;">
198+
<div id="left-controls" class="prevent-select">
199+
<div class="dpad">
200+
<button class="left" ontouchstart="simulateKey(65, true)" ontouchend="simulateKey(65, false)"><i
201+
class="material-symbols">arrow_left</i></button>
202+
<button class="up" ontouchstart="simulateKey(87, true)" ontouchend="simulateKey(87, false)"><i
203+
class="material-symbols">arrow_drop_up</i></button>
204+
<button class="right" ontouchstart="simulateKey(68, true)" ontouchend="simulateKey(68, false)"><i
205+
class="material-symbols">arrow_right</i></button>
206+
<button class="down" ontouchstart="simulateKey(83, true)" ontouchend="simulateKey(83, false)"><i
207+
class="material-symbols">arrow_drop_down</i></button>
208+
<button class="shoot" ontouchstart="simulateKey(32, true)" ontouchend="simulateKey(32, false)"><i
209+
class="material-symbols">swords</i></button>
210+
</div>
211+
212+
</div>
213+
<div id="right-controls" class="prevent-select">
214+
<div class="dpad">
215+
<button class="left" ontouchstart="simulateKey(37, true)" ontouchend="simulateKey(37, false)"><i
216+
class="material-symbols">arrow_left</i></button>
217+
<button class="up" ontouchstart="simulateKey(38, true)" ontouchend="simulateKey(38, false)"><i
218+
class="material-symbols">arrow_drop_up</i></button>
219+
<button class="right" ontouchstart="simulateKey(39, true)" ontouchend="simulateKey(39, false)"><i
220+
class="material-symbols">arrow_right</i></button>
221+
<button class="down" ontouchstart="simulateKey(40, true)" ontouchend="simulateKey(40, false)"><i
222+
class="material-symbols">arrow_drop_down</i></button>
223+
<button class="shoot" ontouchstart="simulateKey(76, true)" ontouchend="simulateKey(76, false)"><i
224+
class="material-symbols">swords</i></button>
225+
</div>
226+
</div>
227+
</div>
228+
229+
<progress value="0" max="100" id="progress" hidden=1></progress>
230+
231+
<script type='text/javascript'>
232+
var statusElement = document.getElementById('status');
233+
var progressElement = document.getElementById('progress');
234+
var spinnerElement = document.getElementById('spinner');
235+
var canvasElement = document.getElementById('canvas');
236+
var outputElement = document.getElementById('output');
237+
if (outputElement) outputElement.value = ''; // clear browser cache
238+
239+
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
240+
// application robust, you may want to override this behavior before shipping!
241+
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
242+
canvasElement.addEventListener("webglcontextlost", (e) => {
243+
alert('WebGL context lost. You will need to reload the page.');
244+
e.preventDefault();
245+
}, false);
246+
247+
var Module = {
248+
print(...args) {
249+
// These replacements are necessary if you render to raw HTML
250+
//text = text.replace(/&/g, "&amp;");
251+
//text = text.replace(/</g, "&lt;");
252+
//text = text.replace(/>/g, "&gt;");
253+
//text = text.replace('\n', '<br>', 'g');
254+
console.log(...args);
255+
if (outputElement) {
256+
var text = args.join(' ');
257+
outputElement.value += text + "\n";
258+
outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
259+
}
260+
},
261+
canvas: canvasElement,
262+
setStatus(text) {
263+
Module.setStatus.last ??= { time: Date.now(), text: '' };
264+
if (text === Module.setStatus.last.text) return;
265+
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
266+
var now = Date.now();
267+
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
268+
Module.setStatus.last.time = now;
269+
Module.setStatus.last.text = text;
270+
if (m) {
271+
text = m[1];
272+
progressElement.value = parseInt(m[2]) * 100;
273+
progressElement.max = parseInt(m[4]) * 100;
274+
progressElement.hidden = false;
275+
spinnerElement.hidden = false;
276+
} else {
277+
progressElement.value = null;
278+
progressElement.max = null;
279+
progressElement.hidden = true;
280+
if (!text) spinnerElement.hidden = true;
281+
}
282+
statusElement.innerHTML = text;
283+
},
284+
totalDependencies: 0,
285+
monitorRunDependencies(left) {
286+
this.totalDependencies = Math.max(this.totalDependencies, left);
287+
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies - left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
288+
}
289+
};
290+
Module.setStatus('Downloading...');
291+
window.onerror = () => {
292+
Module.setStatus('Exception thrown, see JavaScript console');
293+
spinnerElement.style.display = 'none';
294+
Module.setStatus = (text) => {
295+
if (text) console.error('[post-exception status] ' + text);
296+
};
297+
};
298+
299+
// Detect if it's a touch device (Android or otherwise)
300+
if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
301+
// Detect Android specifically
302+
const isAndroid = /Android/i.test(navigator.userAgent);
303+
if (isAndroid) {
304+
// If it's Android, display the touch controls and hide the fullscreen button
305+
document.getElementById('touch-controls').style.visibility = 'visible';
306+
document.getElementById('fullscreen-btn').style.visibility = 'hidden';
307+
}
308+
}
309+
310+
function simulateKey(keyCode, isDown) {
311+
const event = new KeyboardEvent(isDown ? 'keydown' : 'keyup', {
312+
keyCode: keyCode,
313+
which: keyCode,
314+
bubbles: true,
315+
cancelable: true
316+
});
317+
document.dispatchEvent(event);
318+
}
319+
</script>
320+
321+
{{{ SCRIPT }}}
322+
</body>

0 commit comments

Comments
 (0)