use crate::compositor::Plane;
use crate::framework::theme::Theme;
use crate::input::event::{KeyEvent, MouseEventKind};
use ratatui::layout::Rect;
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::time::Instant;
pub trait Scene: Any {
fn scene_id(&self) -> &str;
fn on_enter(&mut self) {}
fn on_exit(&mut self) {}
fn on_resume(&mut self) {}
fn on_pause(&mut self) {}
fn render(&self, area: Rect) -> Plane;
fn handle_key(&mut self, key: KeyEvent) -> bool;
fn handle_mouse(&mut self, kind: MouseEventKind, col: u16, row: u16) -> bool;
fn on_theme_change(&mut self, _theme: &Theme) {}
fn needs_render(&self) -> bool;
fn mark_dirty(&mut self);
fn clear_dirty(&mut self);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SceneTransition {
Fade,
SlideLeft,
SlideRight,
SlideUp,
SlideDown,
None,
}
struct TransitionState {
from_scene: String,
to_scene: String,
progress: f32,
duration_ms: f32,
elapsed_ms: f32,
transition: SceneTransition,
}
#[derive(Clone, Debug)]
pub enum NavigationEvent {
Navigated(Option<String>, String),
Popped(String, String),
Replaced(String, String),
}
pub struct SceneRouter {
scenes: HashMap<String, Box<dyn Scene>>,
stack: Vec<String>,
dirty: Cell<bool>,
theme: Option<Theme>,
transition: RefCell<Option<TransitionState>>,
default_transition: SceneTransition,
default_duration_ms: f32,
last_render: RefCell<Option<Instant>>,
}
impl SceneRouter {
pub fn new() -> Self {
Self {
scenes: HashMap::new(),
stack: Vec::new(),
dirty: Cell::new(false),
theme: None,
transition: RefCell::new(None),
default_transition: SceneTransition::Fade,
default_duration_ms: 200.0,
last_render: RefCell::new(None),
}
}
pub fn register(&mut self, id: &str, scene: Box<dyn Scene>) {
self.scenes.insert(id.to_string(), scene);
}
pub fn unregister(&mut self, id: &str) -> Option<Box<dyn Scene>> {
self.scenes.remove(id)
}
pub fn has_scene(&self, id: &str) -> bool {
self.scenes.contains_key(id)
}
pub fn with_default_transition(mut self, transition: SceneTransition) -> Self {
self.default_transition = transition;
self
}
pub fn with_default_duration(mut self, ms: f32) -> Self {
self.default_duration_ms = ms;
self
}
pub fn push_with_transition(&mut self, id: &str, transition: SceneTransition, duration_ms: f32) {
if !self.scenes.contains_key(id) {
return;
}
if let Some(current_id) = self.stack.last().cloned() {
if transition != SceneTransition::None {
*self.transition.borrow_mut() = Some(TransitionState {
from_scene: current_id,
to_scene: id.to_string(),
progress: 0.0,
duration_ms,
elapsed_ms: 0.0,
transition,
});
}
}
if let Some(current_id) = self.stack.last() {
if let Some(scene) = self.scenes.get_mut(current_id) {
scene.on_pause();
}
}
self.stack.push(id.to_string());
if let Some(scene) = self.scenes.get_mut(id) {
scene.on_enter();
if let Some(ref theme) = self.theme {
scene.on_theme_change(theme);
}
}
self.dirty.set(true);
}
pub fn push(&mut self, id: &str) {
let transition = self.default_transition;
let duration = self.default_duration_ms;
self.push_with_transition(id, transition, duration);
}
pub fn pop(&mut self) -> bool {
if self.stack.len() <= 1 {
return false;
}
let from = self.stack.pop().unwrap();
if let Some(scene) = self.scenes.get_mut(&from) {
scene.on_exit();
}
if let Some(to) = self.stack.last() {
let to = to.clone();
if let Some(scene) = self.scenes.get_mut(&to) {
scene.on_resume();
}
}
self.dirty.set(true);
true
}
pub fn replace(&mut self, id: &str) {
if !self.scenes.contains_key(id) {
return;
}
if let Some(old_id) = self.stack.last().cloned() {
if let Some(scene) = self.scenes.get_mut(&old_id) {
scene.on_exit();
}
}
if let Some(last) = self.stack.last_mut() {
*last = id.to_string();
} else {
self.stack.push(id.to_string());
}
if let Some(scene) = self.scenes.get_mut(id) {
scene.on_enter();
if let Some(ref theme) = self.theme {
scene.on_theme_change(theme);
}
}
self.dirty.set(true);
}
pub fn go(&mut self, id: &str) {
if !self.scenes.contains_key(id) {
return;
}
for scene_id in &self.stack {
if let Some(scene) = self.scenes.get_mut(scene_id) {
scene.on_exit();
}
}
self.stack.clear();
self.stack.push(id.to_string());
if let Some(scene) = self.scenes.get_mut(id) {
scene.on_enter();
if let Some(ref theme) = self.theme {
scene.on_theme_change(theme);
}
}
self.dirty.set(true);
}
pub fn navigate_to(&mut self, path: &str) {
let segments: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if segments.is_empty() {
return;
}
self.go(segments[0]);
for segment in &segments[1..] {
self.push(segment);
}
}
pub fn current_path(&self) -> String {
self.stack.join("/")
}
pub fn current(&self) -> Option<&str> {
self.stack.last().map(|s| s.as_str())
}
pub fn stack_depth(&self) -> usize {
self.stack.len()
}
pub fn can_go_back(&self) -> bool {
self.stack.len() > 1
}
pub fn is_registered(&self, id: &str) -> bool {
self.scenes.contains_key(id)
}
pub fn get_scene(&self, id: &str) -> Option<&dyn Scene> {
self.scenes.get(id).map(|s| s.as_ref())
}
pub fn get_scene_mut(&mut self, id: &str) -> Option<&mut dyn Scene> {
self.scenes.get_mut(id).map(|s| s.as_mut())
}
pub fn render(&self, area: Rect) -> Plane {
let dt_ms = {
let now = Instant::now();
let mut last = self.last_render.borrow_mut();
let dt = last.map(|t| {
let elapsed = now.duration_since(t);
elapsed.as_secs_f32() * 1000.0
}).unwrap_or(16.0); *last = Some(now);
dt
};
let transition_active = self.tick_transition_internal(dt_ms);
if transition_active {
let trans = self.transition.borrow();
if let Some(ref trans) = *trans {
let from_plane = self.scenes.get(&trans.from_scene)
.map(|s| s.render(area))
.unwrap_or_else(|| Plane::new(0, area.width, area.height));
let to_plane = self.scenes.get(&trans.to_scene)
.map(|s| s.render(area))
.unwrap_or_else(|| Plane::new(0, area.width, area.height));
return Self::blend_planes(from_plane, to_plane, trans.progress, trans.transition);
}
}
if let Some(id) = self.stack.last() {
if let Some(scene) = self.scenes.get(id) {
return scene.render(area);
}
}
Plane::new(0, area.width, area.height)
}
pub fn tick_transition(&mut self, dt_ms: f32) -> bool {
self.tick_transition_internal(dt_ms)
}
fn tick_transition_internal(&self, dt_ms: f32) -> bool {
let mut trans_opt = self.transition.borrow_mut();
if let Some(ref mut trans) = *trans_opt {
trans.elapsed_ms += dt_ms;
trans.progress = (trans.elapsed_ms / trans.duration_ms).min(1.0);
if trans.progress >= 1.0 {
*trans_opt = None;
self.dirty.set(true);
return false;
}
self.dirty.set(true);
return true;
}
false
}
pub fn is_transitioning(&self) -> bool {
self.transition.borrow().is_some()
}
fn blend_planes(from: Plane, to: Plane, progress: f32, transition: SceneTransition) -> Plane {
let width = from.width;
let height = from.height;
let mut result = Plane::new(0, width, height);
match transition {
SceneTransition::Fade => {
let threshold = (progress * 10.0) as u8;
for i in 0..from.cells.len().min(to.cells.len()) {
let use_to = (i % 10) < threshold as usize || progress >= 1.0;
result.cells[i] = if use_to { to.cells[i] } else { from.cells[i] };
}
}
SceneTransition::SlideLeft => {
let offset = (width as f32 * progress) as u16;
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
let from_x = x.saturating_add(offset);
let to_x = x.saturating_sub(width - offset);
if from_x < width && from_x < from.width {
let from_idx = (y * from.width + from_x) as usize;
if from_idx < from.cells.len() {
result.cells[idx] = from.cells[from_idx];
}
}
if to_x < width && to_x < to.width {
let to_idx = (y * to.width + to_x) as usize;
if to_idx < to.cells.len() {
result.cells[idx] = to.cells[to_idx];
}
}
}
}
}
SceneTransition::SlideRight => {
let offset = (width as f32 * progress) as u16;
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) as usize;
let from_x = x.saturating_sub(offset);
let to_x = x.saturating_add(width - offset);
if from_x < width && from_x < from.width {
let from_idx = (y * from.width + from_x) as usize;
if from_idx < from.cells.len() {
result.cells[idx] = from.cells[from_idx];
}
}
if to_x < width && to_x < to.width {
let to_idx = (y * to.width + to_x) as usize;
if to_idx < to.cells.len() {
result.cells[idx] = to.cells[to_idx];
}
}
}
}
}
_ => {
for i in 0..from.cells.len().min(to.cells.len()) {
result.cells[i] = if progress > 0.5 { to.cells[i] } else { from.cells[i] };
}
}
}
result
}
pub fn handle_key(&mut self, key: KeyEvent) -> bool {
if let Some(id) = self.stack.last().cloned() {
if let Some(scene) = self.scenes.get_mut(&id) {
return scene.handle_key(key);
}
}
false
}
pub fn handle_mouse(&mut self, kind: MouseEventKind, col: u16, row: u16) -> bool {
if let Some(id) = self.stack.last().cloned() {
if let Some(scene) = self.scenes.get_mut(&id) {
return scene.handle_mouse(kind, col, row);
}
}
false
}
pub fn on_theme_change(&mut self, theme: &Theme) {
self.theme = Some(theme.clone());
for scene in self.scenes.values_mut() {
scene.on_theme_change(theme);
}
self.dirty.set(true);
}
pub fn needs_render(&self) -> bool {
if self.dirty.get() {
return true;
}
if let Some(id) = self.stack.last() {
if let Some(scene) = self.scenes.get(id) {
return scene.needs_render();
}
}
false
}
pub fn mark_dirty(&mut self) {
self.dirty.set(true);
}
pub fn clear_dirty(&mut self) {
self.dirty.set(false);
}
}
impl Default for SceneRouter {
fn default() -> Self {
Self::new()
}
}