Skip to main content

activity_manager/
lib.rs

1use std::fmt::Debug;
2
3/// 定义路由的特征。
4///
5/// 路由负责提供页面的启动模式以及是否为半透明页面,这决定了页面的入栈行为和视图渲染逻辑。
6pub trait Route: Clone + Debug + PartialEq {
7    /// 获取该路由的启动模式。
8    fn launch_mode(&self) -> LaunchMode;
9    /// 指示该路由对应的页面是否为半透明。
10    ///
11    /// 如果为 `false`(不透明),渲染器将不会渲染其底部的页面。
12    fn is_translucent(&self) -> bool;
13}
14
15/// Android 风格的页面启动模式。
16///
17/// 决定了新页面启动时,如何与当前的页面堆栈进行交互。
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum LaunchMode {
20    /// 标准模式。
21    ///
22    /// 每次启动该路由,都会创建一个全新的 Activity 实例并压入栈顶。
23    Standard,
24    /// 栈顶复用模式。
25    ///
26    /// 如果目标路由已经位于栈顶,则不会创建新实例,而是调用栈顶实例的 `on_new_intent`;
27    /// 否则,创建新实例并压入栈顶。
28    SingleTop,
29    /// 栈内复用模式。
30    ///
31    /// 检查整个堆栈。如果目标路由已存在于栈内,则将其上方所有的 Activity 弹出并销毁,
32    /// 使其成为新的栈顶,并调用其 `on_new_intent`。如果不存在,则创建新实例压入栈顶。
33    SingleTask,
34    /// 单例模式。
35    ///
36    /// 堆栈中仅允许存在这唯一一个页面。启动此模式的路由时,会清空栈内的所有其他页面。
37    SingleInstance,
38}
39
40/// 宿主环境抽象。
41///
42/// 为框架提供类型映射,使其可以兼容不同的 UI 引擎(如 Iced、Dioxus 等)。
43pub trait ActivityHost: Sized + 'static {
44    /// 框架全局传递的消息类型。
45    type Message: Send + Debug;
46    /// 全局上下文类型,通常用于存储全局状态或共享服务。
47    type Context;
48    /// 业务路由枚举类型。
49    type Route: Route;
50
51    /// 视图类型。
52    ///
53    /// 利用 GATs(通用关联类型)将生命周期 `'a` 绑定到视图上,
54    /// 允许视图直接安全地借用 `Activity` 内部的数据(例如 Iced 的 `Element<'a, Message>`)。
55    type View<'a>
56    where
57        Self: 'a;
58
59    /// 异步任务类型。
60    ///
61    /// 用于封装页面生命周期和交互中产生的副作用(例如 Iced 的 `Task<Message>`)。
62    type Task;
63
64    /// 创建一个不执行任何操作的空任务。
65    ///
66    /// 作为任务系统的“单位元”,用于那些不需要产生副作用的生命周期钩子。
67    fn empty_task() -> Self::Task;
68
69    /// 将多个异步任务合并为一个宏任务。
70    fn batch_tasks(tasks: Vec<Self::Task>) -> Self::Task;
71}
72
73/// 页面组件特征。
74///
75/// 代表一个拥有完整生命周期、独立状态并能响应全局上下文变化的页面模块。
76pub trait Activity<H: ActivityHost>: Send {
77    /// 渲染页面视图。
78    ///
79    /// 生命周期 `'a` 确保返回的视图可以安全地借用 `&self` 或 `&context` 中的数据。
80    fn view<'a>(&'a self, context: &'a H::Context) -> H::View<'a>;
81
82    /// 处理业务逻辑更新,并返回可能产生的异步任务。
83    fn update(&mut self, _context: &mut H::Context, _message: H::Message) -> H::Task {
84        H::empty_task()
85    }
86
87    /// 生命周期钩子:页面实例被创建时调用。
88    fn on_create(&mut self, _context: &mut H::Context) -> H::Task {
89        H::empty_task()
90    }
91
92    /// 生命周期钩子:页面进入前台、恢复交互时调用。
93    fn on_resume(&mut self, _context: &mut H::Context) -> H::Task {
94        H::empty_task()
95    }
96
97    /// 生命周期钩子:页面被新页面覆盖,或即将被销毁时调用。
98    fn on_pause(&mut self, _context: &mut H::Context) -> H::Task {
99        H::empty_task()
100    }
101
102    /// 生命周期钩子:页面实例被彻底销毁、移出堆栈前调用。
103    fn on_destroy(&mut self, _context: &mut H::Context) -> H::Task {
104        H::empty_task()
105    }
106
107    /// 生命周期钩子:当页面命中 `SingleTop` 或 `SingleTask` 复用机制时调用。
108    fn on_new_intent(&mut self, _context: &mut H::Context, _route: H::Route) -> H::Task {
109        H::empty_task()
110    }
111
112    /// 获取当前 Activity 对应的路由配置。
113    fn route(&self) -> H::Route;
114}
115
116/// 页面堆栈与路由调度器。
117///
118/// 负责管理 Activity 的入栈、出栈,协调生命周期方法的调用,并聚合页面切换产生的异步任务。
119pub struct ActivityManager<H: ActivityHost> {
120    stack: Vec<Box<dyn Activity<H>>>,
121    factory: Box<dyn Fn(&H::Route) -> Box<dyn Activity<H>>>,
122}
123
124impl<H: ActivityHost> ActivityManager<H> {
125    /// 创建一个新的 ActivityManager。
126    ///
127    /// `factory` 是一个闭包,负责根据路由枚举实例化对应的 `Activity`。
128    pub fn new<F>(factory: F) -> Self
129    where
130        F: Fn(&H::Route) -> Box<dyn Activity<H>> + 'static,
131    {
132        log::info!("ActivityManager initialized.");
133        Self {
134            stack: Vec::new(),
135            factory: Box::new(factory),
136        }
137    }
138
139    /// 将消息分发给当前处于栈顶的活动页面。
140    pub fn update(&mut self, context: &mut H::Context, message: H::Message) -> H::Task {
141        if let Some(top) = self.stack.last_mut() {
142            log::trace!("Dispatching update to top activity: {:?}", message);
143            top.update(context, message)
144        } else {
145            log::warn!("Attempted to update but activity stack is empty.");
146            H::empty_task()
147        }
148    }
149
150    /// 启动一个新页面,处理路由逻辑及堆栈变化,并返回相关的异步任务。
151    pub fn start_activity(&mut self, context: &mut H::Context, route: H::Route) -> H::Task {
152        let mode = route.launch_mode();
153        log::info!(
154            "Starting activity with route: {:?}, launch mode: {:?}",
155            route,
156            mode
157        );
158
159        match mode {
160            LaunchMode::Standard => self.push_activity(context, route),
161
162            LaunchMode::SingleTop => {
163                if let Some(top) = self.stack.last_mut() {
164                    if top.route() == route {
165                        log::debug!("SingleTop matched. Reusing top activity.");
166                        return top.on_new_intent(context, route);
167                    }
168                }
169                log::debug!("SingleTop not matched. Pushing as Standard.");
170                self.push_activity(context, route)
171            }
172
173            LaunchMode::SingleTask => {
174                let mut found_index = None;
175                for (i, act) in self.stack.iter().enumerate() {
176                    if act.route() == route {
177                        found_index = Some(i);
178                        break;
179                    }
180                }
181
182                if let Some(index) = found_index {
183                    log::debug!("SingleTask matched at index {}. Clearing top.", index);
184                    let mut tasks = Vec::new();
185
186                    // 1. 销毁目标上方所有页面 (Clear Top)
187                    while self.stack.len() > index + 1 {
188                        tasks.push(self.pop_internal(context));
189                    }
190
191                    // 2. 复用目标页面
192                    let target = &mut self.stack[index];
193                    tasks.push(target.on_new_intent(context, route));
194                    tasks.push(target.on_resume(context));
195
196                    H::batch_tasks(tasks)
197                } else {
198                    log::debug!("SingleTask target not found in stack. Pushing new.");
199                    self.push_activity(context, route)
200                }
201            }
202
203            LaunchMode::SingleInstance => {
204                log::debug!("SingleInstance mode. Clearing entire stack.");
205                let mut tasks = Vec::new();
206                while !self.stack.is_empty() {
207                    tasks.push(self.pop_internal(context));
208                }
209                tasks.push(self.push_activity(context, route));
210                H::batch_tasks(tasks)
211            }
212        }
213    }
214
215    /// 模拟物理返回键逻辑,弹出栈顶页面。
216    ///
217    /// 返回一个元组:
218    /// - `bool`: 如果成功返回上一页则为 `true`;如果栈已空或仅剩一页无法返回则为 `false`。
219    /// - `H::Task`: 退出动作产生的异步任务。
220    pub fn back(&mut self, context: &mut H::Context) -> (bool, H::Task) {
221        if self.stack.len() > 1 {
222            log::info!("Back navigation triggered. Popping top activity.");
223            let mut tasks = Vec::new();
224            tasks.push(self.pop_internal(context)); // 销毁当前页
225
226            if let Some(top) = self.stack.last_mut() {
227                log::debug!("Resuming the new top activity.");
228                tasks.push(top.on_resume(context)); // 恢复前一个页
229            }
230            (true, H::batch_tasks(tasks))
231        } else {
232            log::info!("Back navigation ignored. Stack has 1 or fewer activities.");
233            (false, H::empty_task())
234        }
235    }
236
237    /// 执行视图构建,利用“画家算法”收集需要渲染的页面。
238    ///
239    /// 从栈顶向下查找,直到遇到第一个不透明(`is_translucent() == false`)的页面为止,
240    /// 仅收集并返回可见层级的视图。
241    pub fn views<'a>(&'a self, context: &'a H::Context) -> Vec<H::View<'a>> {
242        log::trace!("Collecting views for rendering.");
243        let mut views = Vec::new();
244        let mut start_index = 0;
245
246        // 从后往前扫描,寻找渲染截断点
247        for (i, act) in self.stack.iter().enumerate().rev() {
248            if !act.route().is_translucent() {
249                start_index = i;
250                log::trace!(
251                    "Found opaque activity at index {}. Stopping downward scan.",
252                    i
253                );
254                break;
255            }
256        }
257
258        for i in start_index..self.stack.len() {
259            views.push(self.stack[i].view(context));
260        }
261        views
262    }
263
264    /// 内部方法:执行入栈逻辑并触发响应生命周期
265    fn push_activity(&mut self, context: &mut H::Context, route: H::Route) -> H::Task {
266        log::debug!("Pushing new activity: {:?}", route);
267        let mut tasks = Vec::new();
268
269        if let Some(top) = self.stack.last_mut() {
270            tasks.push(top.on_pause(context));
271        }
272
273        let mut new_act = (self.factory)(&route);
274        tasks.push(new_act.on_create(context));
275        tasks.push(new_act.on_resume(context));
276
277        self.stack.push(new_act);
278        H::batch_tasks(tasks)
279    }
280
281    /// 内部方法:执行出栈逻辑并触发响应生命周期
282    fn pop_internal(&mut self, context: &mut H::Context) -> H::Task {
283        if let Some(mut act) = self.stack.pop() {
284            log::debug!("Popping activity: {:?}", act.route());
285            let t1 = act.on_pause(context);
286            let t2 = act.on_destroy(context);
287            H::batch_tasks(vec![t1, t2])
288        } else {
289            H::empty_task()
290        }
291    }
292
293    /// 获取当前栈中的页面数量(仅用于测试或状态监控)
294    pub fn stack_len(&self) -> usize {
295        self.stack.len()
296    }
297}
298
299// ============================================================================
300// 单元测试模块
301// ============================================================================
302#[cfg(test)]
303mod tests {
304    use super::*;
305
306    // 1. 定义 Mock 的路由
307    #[derive(Debug, Clone, PartialEq)]
308    enum TestRoute {
309        StandardPage,
310        SingleTopPage,
311        SingleTaskPage,
312        SingleInstancePage,
313        TranslucentPage,
314        OpaquePage,
315    }
316
317    impl Route for TestRoute {
318        fn launch_mode(&self) -> LaunchMode {
319            match self {
320                TestRoute::StandardPage => LaunchMode::Standard,
321                TestRoute::SingleTopPage => LaunchMode::SingleTop,
322                TestRoute::SingleTaskPage => LaunchMode::SingleTask,
323                TestRoute::SingleInstancePage => LaunchMode::SingleInstance,
324                _ => LaunchMode::Standard,
325            }
326        }
327
328        fn is_translucent(&self) -> bool {
329            matches!(self, TestRoute::TranslucentPage)
330        }
331    }
332
333    // 2. 定义 Mock 的宿主环境
334    struct TestHost;
335
336    impl ActivityHost for TestHost {
337        type Message = ();
338        type Context = Vec<String>; // 使用全局 Vec 记录生命周期调用日志
339        type Route = TestRoute;
340        type View<'a> = &'a str;
341        type Task = Vec<String>; // 任务简化为收集命令字符串
342
343        fn empty_task() -> Self::Task {
344            vec![]
345        }
346
347        fn batch_tasks(tasks: Vec<Self::Task>) -> Self::Task {
348            tasks.into_iter().flatten().collect()
349        }
350    }
351
352    // 3. 定义 Mock 的页面 Activity
353    struct TestActivity {
354        route: TestRoute,
355        name: String,
356    }
357
358    impl TestActivity {
359        fn new(route: TestRoute) -> Self {
360            let name = format!("{:?}", route);
361            Self { route, name }
362        }
363    }
364
365    impl Activity<TestHost> for TestActivity {
366        fn view<'a>(
367            &'a self,
368            _context: &'a <TestHost as ActivityHost>::Context,
369        ) -> <TestHost as ActivityHost>::View<'a> {
370            &self.name
371        }
372
373        fn on_create(
374            &mut self,
375            context: &mut <TestHost as ActivityHost>::Context,
376        ) -> <TestHost as ActivityHost>::Task {
377            context.push(format!("on_create: {}", self.name));
378            vec![format!("task_create_{}", self.name)]
379        }
380
381        fn on_resume(
382            &mut self,
383            context: &mut <TestHost as ActivityHost>::Context,
384        ) -> <TestHost as ActivityHost>::Task {
385            context.push(format!("on_resume: {}", self.name));
386            vec![]
387        }
388
389        fn on_pause(
390            &mut self,
391            context: &mut <TestHost as ActivityHost>::Context,
392        ) -> <TestHost as ActivityHost>::Task {
393            context.push(format!("on_pause: {}", self.name));
394            vec![]
395        }
396
397        fn on_destroy(
398            &mut self,
399            context: &mut <TestHost as ActivityHost>::Context,
400        ) -> <TestHost as ActivityHost>::Task {
401            context.push(format!("on_destroy: {}", self.name));
402            vec![format!("task_destroy_{}", self.name)]
403        }
404
405        fn on_new_intent(
406            &mut self,
407            context: &mut <TestHost as ActivityHost>::Context,
408            _route: <TestHost as ActivityHost>::Route,
409        ) -> <TestHost as ActivityHost>::Task {
410            context.push(format!("on_new_intent: {}", self.name));
411            vec![format!("task_new_intent_{}", self.name)]
412        }
413
414        fn route(&self) -> <TestHost as ActivityHost>::Route {
415            self.route.clone()
416        }
417    }
418
419    // 工厂函数
420    fn create_manager() -> ActivityManager<TestHost> {
421        ActivityManager::new(|route: &TestRoute| {
422            Box::new(TestActivity::new(route.clone())) as Box<dyn Activity<TestHost>>
423        })
424    }
425
426    #[test]
427    fn test_launch_mode_standard() {
428        let mut manager = create_manager();
429        let mut context = Vec::new();
430
431        // 连续启动两个相同的 Standard 页面
432        manager.start_activity(&mut context, TestRoute::StandardPage);
433        manager.start_activity(&mut context, TestRoute::StandardPage);
434
435        assert_eq!(manager.stack_len(), 2);
436        // 检查生命周期日志:A 创建 -> A 恢复 -> A 暂停 -> A2 创建 -> A2 恢复
437        assert_eq!(
438            context,
439            vec![
440                "on_create: StandardPage",
441                "on_resume: StandardPage",
442                "on_pause: StandardPage",
443                "on_create: StandardPage",
444                "on_resume: StandardPage"
445            ]
446        );
447    }
448
449    #[test]
450    fn test_launch_mode_single_top() {
451        let mut manager = create_manager();
452        let mut context = Vec::new();
453
454        manager.start_activity(&mut context, TestRoute::StandardPage);
455        manager.start_activity(&mut context, TestRoute::SingleTopPage);
456        context.clear(); // 清空前面的日志以聚焦复用逻辑
457
458        // 再次启动栈顶相同的 SingleTop 页面
459        let task = manager.start_activity(&mut context, TestRoute::SingleTopPage);
460
461        // 栈深度仍然是 2
462        assert_eq!(manager.stack_len(), 2);
463        // 只触发了 on_new_intent
464        assert_eq!(context, vec!["on_new_intent: SingleTopPage"]);
465        // 确保相关的 task 被正确聚合返回
466        assert_eq!(task, vec!["task_new_intent_SingleTopPage"]);
467    }
468
469    #[test]
470    fn test_launch_mode_single_task() {
471        let mut manager = create_manager();
472        let mut context = Vec::new();
473
474        manager.start_activity(&mut context, TestRoute::StandardPage);
475        manager.start_activity(&mut context, TestRoute::SingleTaskPage); // target
476        manager.start_activity(&mut context, TestRoute::StandardPage); // target top 1
477        manager.start_activity(&mut context, TestRoute::StandardPage); // target top 2
478
479        assert_eq!(manager.stack_len(), 4);
480        context.clear();
481
482        // 再次启动 SingleTask 页面
483        let task = manager.start_activity(&mut context, TestRoute::SingleTaskPage);
484
485        // 栈顶的两个 StandardPage 被销毁,栈深恢复为 2
486        assert_eq!(manager.stack_len(), 2);
487        assert_eq!(
488            context,
489            vec![
490                "on_pause: StandardPage",        // top 2 pause
491                "on_destroy: StandardPage",      // top 2 destroy
492                "on_pause: StandardPage",        // top 1 pause
493                "on_destroy: StandardPage",      // top 1 destroy
494                "on_new_intent: SingleTaskPage", // 目标复用
495                "on_resume: SingleTaskPage"
496            ]
497        );
498        // 验证任务批量合并(包含销毁任务和复用任务)
499        assert_eq!(
500            task,
501            vec![
502                "task_destroy_StandardPage",
503                "task_destroy_StandardPage",
504                "task_new_intent_SingleTaskPage"
505            ]
506        );
507    }
508
509    #[test]
510    fn test_launch_mode_single_instance() {
511        let mut manager = create_manager();
512        let mut context = Vec::new();
513
514        manager.start_activity(&mut context, TestRoute::StandardPage);
515        manager.start_activity(&mut context, TestRoute::StandardPage);
516
517        // 启动 SingleInstance
518        manager.start_activity(&mut context, TestRoute::SingleInstancePage);
519
520        // 栈被清空,仅剩下 SingleInstance
521        assert_eq!(manager.stack_len(), 1);
522        assert_eq!(manager.stack[0].route(), TestRoute::SingleInstancePage);
523    }
524
525    #[test]
526    fn test_back_navigation() {
527        let mut manager = create_manager();
528        let mut context = Vec::new();
529
530        manager.start_activity(&mut context, TestRoute::StandardPage); // root
531        manager.start_activity(&mut context, TestRoute::SingleTopPage); // top
532        context.clear();
533
534        let (success, task) = manager.back(&mut context);
535        assert!(success);
536        assert_eq!(manager.stack_len(), 1);
537        assert_eq!(
538            context,
539            vec![
540                "on_pause: SingleTopPage",
541                "on_destroy: SingleTopPage",
542                "on_resume: StandardPage" // 前一个页面恢复
543            ]
544        );
545        assert_eq!(task, vec!["task_destroy_SingleTopPage"]);
546
547        // 栈中仅剩一页,无法继续 back
548        context.clear();
549        let (success, _) = manager.back(&mut context);
550        assert!(!success);
551        assert!(context.is_empty());
552    }
553
554    #[test]
555    fn test_views_painters_algorithm() {
556        let mut manager = create_manager();
557        let mut context = Vec::new();
558
559        manager.start_activity(&mut context, TestRoute::OpaquePage);
560        manager.start_activity(&mut context, TestRoute::TranslucentPage);
561        manager.start_activity(&mut context, TestRoute::TranslucentPage);
562
563        // Opaque -> Translucent -> Translucent
564        // 应该渲染全部三个
565        let views = manager.views(&context);
566        assert_eq!(views.len(), 3);
567        assert_eq!(
568            views,
569            vec!["OpaquePage", "TranslucentPage", "TranslucentPage"]
570        );
571
572        // 在顶部推入一个不透明页面: Opaque -> Trans -> Trans -> Opaque
573        manager.start_activity(&mut context, TestRoute::OpaquePage);
574        let views_top = manager.views(&context);
575
576        // 发生裁剪,仅渲染顶部那个 Opaque
577        assert_eq!(views_top.len(), 1);
578        assert_eq!(views_top, vec!["OpaquePage"]);
579    }
580}