Skip to content

Commit cad772d

Browse files
committed
feat: responsive layout
1 parent 5329cb3 commit cad772d

File tree

4 files changed

+117
-26
lines changed

4 files changed

+117
-26
lines changed

godot/scenes/camera_3d.gd

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@ var _target_node: Node3D = null
77
_set_target(value)
88
get:
99
return _get_target()
10-
@export var orbit_radius: float = 20.0
10+
@export var orbit_radius: float = 5.0
1111
@export var orbit_sensitivity: float = 0.01
1212
@export var zoom_sensitivity: float = 0.1
1313
@export var min_zoom: float = 5.0
1414
@export var max_zoom: float = 50.0
15+
@export var pinch_zoom_sensitivity: float = 2.0
1516

1617
var orbit_angles = Vector2.ZERO # Stores pitch and yaw angles
1718

19+
# Touch tracking for pinch gestures
20+
var touch_points = {}
21+
var initial_pinch_distance = 0.0
22+
var is_pinching = false
23+
1824
# Called when the node enters the scene tree for the first time.
1925
func _ready() -> void:
2026
# Initialize with no target
@@ -40,6 +46,7 @@ func _unhandled_input(event: InputEvent) -> void:
4046
if not target_node:
4147
return
4248

49+
# Handle mouse controls
4350
if event is InputEventMouseMotion and event.button_mask == MOUSE_BUTTON_LEFT:
4451
# Update orbit angles based on mouse movement
4552
orbit_angles.x += event.relative.y * orbit_sensitivity # Reversed y movement
@@ -60,12 +67,70 @@ func _unhandled_input(event: InputEvent) -> void:
6067
eff = min(zoom_sensitivity, 0.01)
6168
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
6269
# Zoom in multiplicatively and clamp to target-aware limits
63-
orbit_radius = clamp(orbit_radius * (1.0 - eff), limits.x, limits.y)
70+
orbit_radius = clamp(orbit_radius / (1.0 + eff), limits.x, limits.y)
6471
_update_camera_position()
6572
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
6673
# Zoom out multiplicatively and clamp to target-aware limits
6774
orbit_radius = clamp(orbit_radius * (1.0 + eff), limits.x, limits.y)
6875
_update_camera_position()
76+
77+
# Handle touch controls
78+
elif event is InputEventScreenTouch:
79+
if event.pressed:
80+
# Touch down - add to tracking
81+
touch_points[event.index] = event.position
82+
83+
# Check if we now have two touches for pinching
84+
if touch_points.size() == 2:
85+
is_pinching = true
86+
var positions = touch_points.values()
87+
initial_pinch_distance = positions[0].distance_to(positions[1])
88+
else:
89+
# Touch up - remove from tracking
90+
touch_points.erase(event.index)
91+
92+
# Stop pinching if we don't have two touches
93+
if touch_points.size() < 2:
94+
is_pinching = false
95+
initial_pinch_distance = 0.0
96+
97+
elif event is InputEventScreenDrag:
98+
# Update touch position
99+
if event.index in touch_points:
100+
touch_points[event.index] = event.position
101+
102+
# Handle pinch gesture
103+
if is_pinching and touch_points.size() == 2:
104+
var positions = touch_points.values()
105+
var current_distance = positions[0].distance_to(positions[1])
106+
107+
if initial_pinch_distance > 0:
108+
var zoom_factor = current_distance / initial_pinch_distance
109+
var limits := _get_zoom_limits()
110+
111+
# Adjust sensitivity based on target type
112+
var eff := pinch_zoom_sensitivity * 0.01 # Scale down for smoother control
113+
if target_node and "radius" in target_node:
114+
eff = min(eff, 0.005) # Even smaller for planets
115+
116+
# Apply zoom based on pinch distance change
117+
var new_radius = orbit_radius / (1.0 + (zoom_factor - 1.0) * eff)
118+
orbit_radius = clamp(new_radius, limits.x, limits.y)
119+
120+
_update_camera_position()
121+
122+
# Update for next frame
123+
initial_pinch_distance = current_distance
124+
125+
# Single finger drag for orbit controls (only if not pinching)
126+
elif not is_pinching and event.index == 0:
127+
orbit_angles.x += event.relative.y * orbit_sensitivity * 2.0 # Slightly more sensitive for touch
128+
orbit_angles.y -= event.relative.x * orbit_sensitivity * 2.0
129+
130+
# Clamp vertical rotation to avoid flipping
131+
orbit_angles.x = clamp(orbit_angles.x, -PI / 2, PI / 2)
132+
133+
_update_camera_position()
69134

70135
# Called every frame. 'delta' is the elapsed time since the previous frame.
71136
func _process(_delta: float) -> void:

web/src/lib/components/GodotEngine.svelte

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,37 @@
108108
109109
const rect = wrapEl.getBoundingClientRect();
110110
const dpr = window.devicePixelRatio || 1;
111+
112+
// Target aspect ratio for 480i (4:3)
113+
const targetAspectRatio = 4 / 3;
114+
const containerAspectRatio = rect.width / rect.height;
115+
116+
let canvasWidth, canvasHeight;
117+
118+
// Calculate canvas size to fill container while maintaining 4:3 aspect ratio
119+
// This works like CSS object-fit: cover - fills container, crops excess
120+
if (containerAspectRatio > targetAspectRatio) {
121+
// Container is wider than 4:3, so fit to width and crop height
122+
canvasWidth = rect.width;
123+
canvasHeight = rect.width / targetAspectRatio;
124+
} else {
125+
// Container is taller than 4:3, so fit to height and crop width
126+
canvasHeight = rect.height;
127+
canvasWidth = rect.height * targetAspectRatio;
128+
}
111129
112130
// Set the display size (CSS pixels)
113-
canvasEl.style.width = rect.width + 'px';
114-
canvasEl.style.height = rect.height + 'px';
131+
canvasEl.style.width = canvasWidth + 'px';
132+
canvasEl.style.height = canvasHeight + 'px';
133+
134+
// Center the canvas in the container
135+
canvasEl.style.left = '50%';
136+
canvasEl.style.top = '50%';
137+
canvasEl.style.transform = 'translate(-50%, -50%)';
115138
116139
// Set the actual size in memory (scaled for high-DPI displays)
117-
canvasEl.width = rect.width * dpr;
118-
canvasEl.height = rect.height * dpr;
140+
canvasEl.width = canvasWidth * dpr;
141+
canvasEl.height = canvasHeight * dpr;
119142
120143
// Notify the Godot engine if it's running
121144
if (engine && engine.requestDisplayRefresh) {
@@ -241,7 +264,7 @@
241264
}
242265
</script>
243266

244-
<div class="relative h-full w-full" bind:this={wrapEl}>
267+
<div class="relative h-full w-full bg-[#131318] overflow-hidden" bind:this={wrapEl}>
245268
<canvas
246269
id="canvas"
247270
class="absolute inset-0 block"
@@ -292,5 +315,6 @@
292315
display: block;
293316
outline: none;
294317
background: transparent;
318+
position: absolute;
295319
}
296320
</style>

web/src/lib/components/MemoryViewer.svelte

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
}
115115
</script>
116116

117-
<div class="flex h-full flex-col gap-3 font-mono text-sm">
117+
<div class="flex h-full flex-col gap-3 font-mono text-sm max-w-2xl mx-auto">
118118
<!-- Registers Display -->
119119
<div class="flex flex-col gap-2">
120120
<div class="border-b border-[#ffb86b]/20 pb-1 text-xs tracking-[0.2em] text-[#ffb86b]/80">
@@ -211,18 +211,18 @@
211211
<div
212212
class="grid-cols-17 sticky top-0 mb-1 grid gap-1 bg-[#131318] text-xs text-[#ffb86b]/40"
213213
>
214-
<div></div>
215-
<!-- Empty cell for row headers -->
214+
<div class="text-right"></div>
215+
<!-- Empty cell for row headers, aligned right like row headers -->
216216
{#each Array(16) as _, i}
217-
<div class="text-center">{formatHex(i, 1)}</div>
217+
<div class="text-center p-1">{formatHex(i, 1)}</div>
218218
{/each}
219219
</div>
220220

221221
<!-- Memory rows -->
222222
{#each Array(16) as _, row}
223223
<div class="grid-cols-17 mb-1 grid gap-1 text-xs">
224224
<!-- Row header -->
225-
<div class="text-right text-[#ffb86b]/40">
225+
<div class="text-right text-[#ffb86b]/40 p-1">
226226
{formatHex(currentPage * 16 + row, 1)}0:
227227
</div>
228228

@@ -257,6 +257,6 @@
257257
<style>
258258
/* Custom grid for 16 columns + 1 header column */
259259
.grid-cols-17 {
260-
grid-template-columns: auto repeat(16, 1fr);
260+
grid-template-columns: 3ch repeat(16, minmax(2ch, 1fr));
261261
}
262262
</style>

web/src/routes/+page.svelte

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ inner_loop:
113113
<link rel="manifest" href="SpessComputer.manifest.json" />
114114
</svelte:head>
115115

116-
<main class="flex gap-4 p-4">
117-
<!-- LEFT: Canvas panel with 4:3 aspect ratio -->
118-
<section class="relative bg-[#101014] shadow-[inset_0_0_0_2px_rgba(255,184,107,0.15)]" style="width: min(calc((100vh - 2rem) * 4/3), 60vw); height: min(calc(100vh - 2rem), calc(60vw * 3/4));">
116+
<main class="flex flex-col lg:flex-row gap-4 p-4">
117+
<!-- VISUAL LINK: Top on mobile, left on desktop -->
118+
<section class="relative bg-[#101014] shadow-[inset_0_0_0_2px_rgba(255,184,107,0.15)] w-full lg:w-auto order-first flex-shrink-0 lg:aspect-[4/3] lg:h-[calc(100vh-2rem)] lg:max-w-[60vw]" style="height: 50vh;">
119119
<div
120120
class="flex items-center justify-between border-b border-[#ffb86b]/30 px-3 py-2 text-xs tracking-[0.2em]"
121121
>
@@ -134,8 +134,8 @@ inner_loop:
134134
{@render Corner('br')}
135135
</section>
136136

137-
<!-- RIGHT: Tabs panel -->
138-
<section class="relative flex-1 bg-[#101014] shadow-[inset_0_0_0_2px_rgba(255,184,107,0.15)]">
137+
<!-- CONTROL DECK: Bottom on mobile, right on desktop -->
138+
<section class="relative flex-1 bg-[#101014] shadow-[inset_0_0_0_2px_rgba(255,184,107,0.15)] order-last">
139139
<div
140140
class="flex items-center justify-between border-b border-[#ffb86b]/30 px-3 py-2 text-xs tracking-[0.2em]"
141141
>
@@ -144,7 +144,7 @@ inner_loop:
144144

145145
<!-- Tabs -->
146146
<div
147-
class="flex gap-2 border-b border-[#ffb86b]/20 bg-[#0f0f12] px-3 py-2"
147+
class="flex gap-1 sm:gap-2 border-b border-[#ffb86b]/20 bg-[#0f0f12] px-2 sm:px-3 py-2"
148148
role="tablist"
149149
aria-label="Controls"
150150
onkeydown={onTabsKey}
@@ -156,7 +156,7 @@ inner_loop:
156156

157157
<!-- Panels -->
158158
<div
159-
class="h-[calc(60vh-6rem)] bg-[#131318] p-3 shadow-[inset_0_0_0_1px_rgba(255,184,107,0.15),0_0_0_0_1px_rgba(255,184,107,0.08)] lg:h-[calc(78vh-6rem)]"
159+
class="min-h-[80vh] lg:h-[calc(60vh-6rem)] xl:h-[calc(78vh-6rem)] bg-[#131318] p-2 sm:p-3 shadow-[inset_0_0_0_1px_rgba(255,184,107,0.15),0_0_0_0_1px_rgba(255,184,107,0.08)]"
160160
>
161161
{#if activeTab === 'tab1'}
162162
<div
@@ -167,12 +167,13 @@ inner_loop:
167167
class="flex h-full min-h-0 flex-col gap-3"
168168
in:fade={{ duration: 150 }}
169169
>
170-
<div class="flex-1 min-h-0 overflow-y-auto">
170+
<div class="flex-1 min-h-[60vh] lg:min-h-0 overflow-y-auto">
171171
<Asm6502Editor bind:value={source} className="h-full" />
172172
</div>
173173
<button
174174
onclick={() => handleRespawnShip()}
175-
class="transform border border-[#ffb86b]/40 bg-[#0f0f12] px-4 py-2 tracking-[0.18em]
175+
class="transform border border-[#ffb86b]/40 bg-[#0f0f12] px-3 sm:px-4 py-2
176+
text-xs sm:text-sm tracking-[0.15em] sm:tracking-[0.18em]
176177
text-[#ffb86b]/80 shadow-[inset_0_0_0_1px_rgba(255,184,107,0.35)] transition hover:text-[#ffb86b]
177178
hover:brightness-110 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50"
178179
disabled={isRespawning}
@@ -202,7 +203,7 @@ inner_loop:
202203
in:fade={{ duration: 150 }}
203204
>
204205
{#if engine}
205-
<div class="min-h-0 flex-1">
206+
<div class="min-h-[60vh] lg:min-h-0 flex-1 overflow-y-auto">
206207
<MemoryViewer />
207208
</div>
208209
<div class="mt-3">
@@ -219,7 +220,7 @@ inner_loop:
219220
hidden
220221
>
221222
{#if engine}
222-
<div class="min-h-0 flex-1" style="display: none;">
223+
<div class="min-h-[60vh] lg:min-h-0 flex-1 overflow-y-auto" style="display: none;">
223224
<MemoryViewer />
224225
</div>
225226
<div class="mt-3" style="display: none;">
@@ -260,9 +261,10 @@ inner_loop:
260261
aria-controls={`tab-panel-${id.slice(-1)}`}
261262
tabindex={activeTab === id ? 0 : -1}
262263
onclick={() => (activeTab = id)}
263-
class="transform border border-[#ffb86b]/40 bg-[#0f0f12] px-3 py-2 tracking-[0.18em]
264+
class="transform border border-[#ffb86b]/40 bg-[#0f0f12] px-2 sm:px-3 py-1.5 sm:py-2
265+
text-xs sm:text-sm tracking-[0.15em] sm:tracking-[0.18em] flex-1 sm:flex-none
264266
text-[#ffb86b]/80 shadow-[inset_0_0_0_1px_rgba(255,184,107,0.35)] transition hover:brightness-110
265-
active:scale-[0.98] data-[active=true]:text-[#ffb86b]"
267+
active:scale-[0.98] data-[active=true]:text-[#ffb86b] whitespace-nowrap"
266268
data-active={activeTab === id}
267269
>
268270
{label}

0 commit comments

Comments
 (0)