Skip to main content

game_gem/
scene.rs

1//! Scene management system.
2//!
3//! Advantages over macroquad (which has none):
4//! - **Stack-based scene transitions** with fade effects
5//! - **Scene parameters** — pass data between scenes
6//! - **Transition effects** — fade, slide, custom
7//! - **On-enter / on-exit** hooks
8//! - **Pause / resume** for overlay scenes
9
10use crate::engine::Context;
11
12// ─────────────────────────────────────────────
13// Scene trait
14// ─────────────────────────────────────────────
15
16/// A scene is a self-contained game state (menu, gameplay, pause screen, etc.).
17///
18/// Implement this trait for each major game state.
19/// The scene manager handles transitions automatically.
20pub trait Scene: SceneAsAny + 'static {
21    /// Called when the scene is first pushed onto the stack.
22    fn on_enter(&mut self, _ctx: &mut Context) {}
23
24    /// Called when the scene is popped off the stack.
25    fn on_exit(&mut self, _ctx: &mut Context) {}
26
27    /// Called when the scene below is covered by another scene.
28    fn on_pause(&mut self, _ctx: &mut Context) {}
29
30    /// Called when the scene above is popped, revealing this one.
31    fn on_resume(&mut self, _ctx: &mut Context) {}
32
33    /// Update logic (called once per frame).
34    fn update(&mut self, ctx: &mut Context);
35
36    /// Render (called once per frame, after update).
37    fn render(&mut self, ctx: &mut Context);
38
39    /// Handle window events (optional override).
40    fn handle_event(&mut self, _ctx: &mut Context, _event: &SceneEvent) {}
41}
42
43/// Events that can be passed to scenes.
44#[derive(Debug, Clone)]
45pub enum SceneEvent {
46    /// Window was resized.
47    WindowResized { width: u32, height: u32 },
48    /// Window gained focus.
49    GainedFocus,
50    /// Window lost focus.
51    LostFocus,
52    /// A custom event with a string tag.
53    Custom(String),
54}
55
56// ─────────────────────────────────────────────
57// Transition effects
58// ─────────────────────────────────────────────
59
60/// Transition effect between scenes.
61#[derive(Debug, Clone)]
62pub enum Transition {
63    /// No transition (instant switch).
64    None,
65    /// Fade to black and back.
66    Fade { duration: f32, color: [f32; 4] },
67    /// Slide the new scene in from a direction.
68    Slide { duration: f32, direction: SlideDirection },
69}
70
71#[derive(Debug, Clone, Copy)]
72pub enum SlideDirection {
73    Left,
74    Right,
75    Up,
76    Down,
77}
78
79// ─────────────────────────────────────────────
80// Scene Manager
81// ─────────────────────────────────────────────
82
83/// State of a transition.
84#[derive(Debug, Clone, Copy, PartialEq)]
85enum TransitionPhase {
86    /// No transition active.
87    Idle,
88    /// Transitioning out (old scene fading/closing).
89    Out,
90    /// Transitioning in (new scene fading/opening).
91    In,
92}
93
94/// Manages a stack of scenes with transitions.
95///
96/// # Example
97/// ```
98/// let mut scenes = SceneManager::new();
99/// scenes.push(MenuScene::new());
100///
101/// // In game loop:
102/// scenes.update(ctx);
103/// scenes.render(ctx);
104///
105/// // From within a scene:
106/// // scenes.push(GameScene::new(level));
107/// // scenes.pop(); // return to previous scene
108/// // scenes.replace(SettingsScene::new()); // replace current scene
109/// ```
110pub struct SceneManager {
111    stack: Vec<Box<dyn Scene>>,
112    transition: Option<TransitionEffect>,
113    /// Pending scene to push after transition-out completes.
114    pending_push: Option<Box<dyn Scene>>,
115    /// Pending pop after transition-out completes.
116    pending_pop: bool,
117    /// Pending replace after transition-out completes.
118    pending_replace: Option<Box<dyn Scene>>,
119}
120
121#[derive(Debug)]
122struct TransitionEffect {
123    #[allow(dead_code)]
124    kind: Transition,
125    phase: TransitionPhase,
126    timer: f32,
127    half_duration: f32,
128}
129
130impl SceneManager {
131    /// Create a new empty scene manager.
132    pub fn new() -> Self {
133        Self {
134            stack: Vec::new(),
135            transition: None,
136            pending_push: None,
137            pending_pop: false,
138            pending_replace: None,
139        }
140    }
141
142    /// Push a new scene onto the stack.
143    pub fn push<S: Scene>(&mut self, scene: S) {
144        match &self.transition {
145            Some(t) if t.phase != TransitionPhase::Idle => {
146                // Queue the push
147                self.pending_push = Some(Box::new(scene));
148                return;
149            }
150            _ => {}
151        }
152
153        // Pause current scene (split the borrow so we don't alias `self`).
154        let mut fake_ctx = self.make_fake_ctx();
155        if let Some(current) = self.stack.last_mut() {
156            current.on_pause(&mut fake_ctx);
157        }
158
159        self.stack.push(Box::new(scene));
160        if let Some(top) = self.stack.last_mut() {
161            top.on_enter(&mut fake_ctx);
162        }
163    }
164
165    /// Pop the top scene off the stack.
166    pub fn pop(&mut self) {
167        match &self.transition {
168            Some(t) if t.phase != TransitionPhase::Idle => {
169                self.pending_pop = true;
170                return;
171            }
172            _ => {}
173        }
174
175        let mut fake_ctx = self.make_fake_ctx();
176        if let Some(mut scene) = self.stack.pop() {
177            scene.on_exit(&mut fake_ctx);
178        }
179
180        // Resume the scene below
181        if let Some(top) = self.stack.last_mut() {
182            top.on_resume(&mut fake_ctx);
183        }
184    }
185
186    /// Replace the current scene with a new one.
187    pub fn replace<S: Scene>(&mut self, scene: S) {
188        match &self.transition {
189            Some(t) if t.phase != TransitionPhase::Idle => {
190                self.pending_replace = Some(Box::new(scene));
191                return;
192            }
193            _ => {}
194        }
195
196        let mut fake_ctx = self.make_fake_ctx();
197        if let Some(mut old) = self.stack.pop() {
198            old.on_exit(&mut fake_ctx);
199        }
200
201        self.stack.push(Box::new(scene));
202        if let Some(top) = self.stack.last_mut() {
203            top.on_enter(&mut fake_ctx);
204        }
205    }
206
207    /// Push a scene with a transition effect.
208    pub fn push_with_transition<S: Scene>(&mut self, scene: S, transition: Transition) {
209        self.start_transition(transition);
210        self.pending_push = Some(Box::new(scene));
211    }
212
213    /// Pop with a transition effect.
214    pub fn pop_with_transition(&mut self, transition: Transition) {
215        self.start_transition(transition);
216        self.pending_pop = true;
217    }
218
219    /// Get a reference to the current (top) scene, if any.
220    pub fn current(&self) -> Option<&dyn Scene> {
221        self.stack.last().map(|s| s.as_ref())
222    }
223
224    /// Get a mutable reference to the current scene, if any.
225    pub fn current_mut(&mut self) -> Option<&mut dyn Scene> {
226        self.stack.last_mut().map(|s| s.as_mut())
227    }
228
229    /// Get a scene by type from the stack.
230    pub fn find<S: Scene>(&self) -> Option<&S> {
231        for scene in &self.stack {
232            if let Some(s) = scene.as_ref().as_any().downcast_ref::<S>() {
233                return Some(s);
234            }
235        }
236        None
237    }
238
239    /// Number of scenes on the stack.
240    pub fn depth(&self) -> usize {
241        self.stack.len()
242    }
243
244    /// Whether the scene stack is empty.
245    pub fn is_empty(&self) -> bool {
246        self.stack.is_empty()
247    }
248
249    /// Update the top scene and any active transition.
250    pub fn update(&mut self, ctx: &mut Context) {
251        // Update transition. We pull the transition out of `self` for the
252        // duration of the body so we can call `execute_pending(&mut self, ...)`
253        // without an aliased mutable borrow.
254        let mut trans_opt = self.transition.take();
255        if let Some(trans) = &mut trans_opt {
256            trans.timer += ctx.time.delta() as f32;
257            match trans.phase {
258                TransitionPhase::Out => {
259                    if trans.timer >= trans.half_duration {
260                        // Transition-out complete, perform the pending action.
261                        self.execute_pending(ctx);
262                        trans.phase = TransitionPhase::In;
263                        trans.timer = 0.0;
264                    }
265                }
266                TransitionPhase::In => {
267                    if trans.timer >= trans.half_duration {
268                        // transition is finished; drop it.
269                        // (trans_opt stays `Some` but we'll clear below)
270                        trans_opt = None;
271                    }
272                }
273                TransitionPhase::Idle => {}
274            }
275        }
276        self.transition = trans_opt;
277
278        // Update only the top scene
279        if let Some(top) = self.stack.last_mut() {
280            top.update(ctx);
281        }
282    }
283
284    /// Render all visible scenes (the top one, and the one below during transitions).
285    pub fn render(&mut self, ctx: &mut Context) {
286        // During transition-out, render the old scene
287        // During transition-in, render the new scene
288        if let Some(trans) = &self.transition {
289            match trans.phase {
290                TransitionPhase::Out => {
291                    if let Some(scene) = self.stack.last_mut() {
292                        scene.render(ctx);
293                    }
294                    self.render_transition_overlay(ctx, trans, false);
295                }
296                TransitionPhase::In => {
297                    if let Some(scene) = self.stack.last_mut() {
298                        scene.render(ctx);
299                    }
300                    self.render_transition_overlay(ctx, trans, true);
301                }
302                TransitionPhase::Idle => {}
303            }
304        } else if let Some(top) = self.stack.last_mut() {
305            top.render(ctx);
306        }
307    }
308
309    fn start_transition(&mut self, kind: Transition) {
310        let half_duration = match &kind {
311            Transition::None => 0.0,
312            Transition::Fade { duration, .. } => duration / 2.0,
313            Transition::Slide { duration, .. } => duration / 2.0,
314        };
315        self.transition = Some(TransitionEffect {
316            kind,
317            phase: TransitionPhase::Out,
318            timer: 0.0,
319            half_duration,
320        });
321    }
322
323    fn execute_pending(&mut self, ctx: &mut Context) {
324        if let Some(scene) = self.pending_push.take() {
325            if let Some(current) = self.stack.last_mut() {
326                current.on_pause(ctx);
327            }
328            self.stack.push(scene);
329            if let Some(top) = self.stack.last_mut() {
330                top.on_enter(ctx);
331            }
332        }
333
334        if self.pending_pop {
335            self.pending_pop = false;
336            if let Some(mut scene) = self.stack.pop() {
337                scene.on_exit(ctx);
338            }
339            if let Some(top) = self.stack.last_mut() {
340                top.on_resume(ctx);
341            }
342        }
343
344        if let Some(scene) = self.pending_replace.take() {
345            if let Some(mut old) = self.stack.pop() {
346                old.on_exit(ctx);
347            }
348            self.stack.push(scene);
349            if let Some(top) = self.stack.last_mut() {
350                top.on_enter(ctx);
351            }
352        }
353    }
354
355    fn render_transition_overlay(&self, _ctx: &mut Context, trans: &TransitionEffect, _is_in: bool) {
356        // In a real implementation, this would draw a fade overlay or sliding rect
357        // using the graphics module. For now, this is a hook for the rendering backend.
358        let _ = (trans, _is_in); // suppress unused warnings
359    }
360
361    /// Create a minimal context for on_enter/on_exit callbacks.
362    /// In real use, the engine passes the actual context.
363    fn make_fake_ctx(&self) -> Context {
364        Context::default()
365    }
366}
367
368// Helper trait for downcasting scene trait objects.
369//
370// `Scene` extends `SceneAsAny` so that `dyn Scene` carries the `as_any` method
371// in its vtable, allowing runtime downcasting of trait-object pointers.
372//
373// The trait is declared `pub` (but not re-exported from the crate) so that
374// using it as a supertrait bound on the public `Scene` trait does not leak
375// private visibility.
376pub trait SceneAsAny {
377    fn as_any(&self) -> &dyn std::any::Any;
378}
379
380impl<S: Scene + 'static> SceneAsAny for S {
381    fn as_any(&self) -> &dyn std::any::Any { self }
382}