oxframe_components/
code.rs1use std::cell::Cell;
4use std::rc::Rc;
5
6use oxframe_2d::{BaseProps, Code, Muted, Node, NodeBuilder, NodeId, Normal, Txt};
7use oxframe_core::{Seconds, Tween, all};
8
9use crate::host;
10
11pub struct CodeExplainer {
14 root: Node,
15 code: Code,
16 caption: Txt,
17 steps: Rc<Vec<(usize, usize, String)>>,
18 cursor: Rc<Cell<usize>>,
19}
20
21impl CodeExplainer {
22 #[allow(clippy::new_ret_no_self)]
24 pub fn new(code: impl Into<String>) -> CodeExplainerBuilder {
25 CodeExplainerBuilder {
26 base: BaseProps::default(),
27 code: code.into(),
28 steps: Vec::new(),
29 }
30 }
31
32 pub fn enter(&self) -> Tween<f64> {
34 self.root.fade_in()
35 }
36
37 pub fn next(&self) -> impl std::future::Future<Output = ()> + 'static {
39 let n = self.steps.len();
40 let i = self.cursor.get();
41 let (first, last, text) = if n == 0 {
42 (0, 0, String::new())
43 } else {
44 let step = &self.steps[i.min(n - 1)];
45 if i < n {
46 self.cursor.set(i + 1);
47 }
48 (step.0, step.1, step.2.clone())
49 };
50 self.caption.text().set(text);
51 let focus = self.code.focus_lines(first, last, Normal.seconds());
52 let fade = if i == 0 {
53 self.caption.fade_in()
54 } else {
55 self.caption.opacity().to(1.0, Normal)
56 };
57 async move {
58 all((focus, fade)).await;
59 }
60 }
61
62 pub fn exit(&self) -> Tween<f64> {
64 self.root.fade_out()
65 }
66
67 pub fn len(&self) -> usize {
69 self.steps.len()
70 }
71 pub fn is_empty(&self) -> bool {
73 self.steps.is_empty()
74 }
75}
76
77#[must_use = "a builder does nothing until it is passed to `add`"]
79pub struct CodeExplainerBuilder {
80 base: BaseProps,
81 code: String,
82 steps: Vec<(usize, usize, String)>,
83}
84
85impl CodeExplainerBuilder {
86 pub fn step(mut self, first: usize, last: usize, caption: impl Into<String>) -> Self {
88 self.steps.push((first, last, caption.into()));
89 self
90 }
91}
92
93impl NodeBuilder for CodeExplainerBuilder {
94 type Handle = CodeExplainer;
95 fn base(&mut self) -> &mut BaseProps {
96 &mut self.base
97 }
98 fn build(self, _: &oxframe_2d::SceneGraph, parent: NodeId) -> CodeExplainer {
99 let root = host::root(parent, self.base);
100 let code = root.add(
101 Code::new()
102 .code(self.code)
103 .language("rust")
104 .font_size(26.0)
105 .y(-24.0)
106 .name("code"),
107 );
108 let (_, h) = code.size();
109 let caption = root.add(
110 Txt::new()
111 .text(" ")
112 .caption()
113 .fill(Muted)
114 .y(h / 2.0 + 36.0)
115 .opacity(0.0)
116 .name("caption"),
117 );
118 CodeExplainer {
119 root: root.node(),
120 code,
121 caption,
122 steps: Rc::new(self.steps),
123 cursor: Rc::new(Cell::new(0)),
124 }
125 }
126}