1use yog_api::ui::event::keys;
14use yog_api::ui::widget::WidgetKind;
15use yog_api::ui::{LayoutNode, Rect, StateFlags, UiRoot, Widget};
16
17use crate::host::{find_path, Host};
18use crate::{ScreenHandle, View};
19
20pub struct TestHost<W: View> {
22 host: Host<W>,
23}
24
25impl<W: View> TestHost<W> {
26 pub fn new(vm: W::Vm) -> Self {
29 let mut host = Host::new(vm, ScreenHandle::new(W::META), false);
30 host.size = (427.0, 240.0);
31 Self { host }
32 }
33
34 pub fn size(&mut self, w: f32, h: f32) -> &mut Self {
36 self.host.size = (w, h);
37 self
38 }
39
40 pub fn open(&mut self) -> &mut Self {
42 let (w, h) = self.host.size;
43 self.event(&format!("open:{w}:{h}"));
44 self
45 }
46
47 pub fn close(&mut self) -> &mut Self {
49 self.event("close");
50 self
51 }
52
53 pub fn frame(&mut self) -> &mut Self {
55 let (w, h) = self.host.size;
56 self.host.frame(w, h);
57 self
58 }
59
60 pub fn event(&mut self, raw: &str) -> bool {
63 let used = self.host.on_event(raw);
64 self.frame();
65 used
66 }
67
68 pub fn click_path(&mut self, path: &[u16]) -> bool {
70 let Some(r) = self.host.content_node(path).map(|n| n.rect) else {
71 panic!("yog-kit test: nothing laid out at {path:?}");
72 };
73 let (x, y) = (r.x + r.w / 2.0, r.y + r.h / 2.0);
74 self.move_to(x, y);
75 let used = self.event(&format!("click:{x}:{y}"));
76 self.event(&format!("release:{x}:{y}"));
77 used
78 }
79
80 pub fn click_id(&mut self, id: &str) -> bool {
82 let path = self.path(id);
83 self.click_path(&path)
84 }
85
86 pub fn move_to(&mut self, x: f32, y: f32) -> &mut Self {
88 self.event(&format!("move:{x}:{y}"));
89 self
90 }
91
92 pub fn drag(&mut self, points: &[(f32, f32)]) -> bool {
96 let Some((&(x0, y0), rest)) = points.split_first() else {
97 return false;
98 };
99 self.move_to(x0, y0);
100 let used = self.event(&format!("click:{x0}:{y0}"));
101 for &(x, y) in rest {
102 self.event(&format!("drag:{x}:{y}"));
103 }
104 let (x, y) = points[points.len() - 1];
105 self.event(&format!("release:{x}:{y}"));
106 used
107 }
108
109 pub fn scroll(&mut self, dy: f32) -> bool {
111 self.event(&format!("scroll:{dy}"))
112 }
113
114 pub fn key(&mut self, key: i32) -> bool {
116 self.key_mods(key, 0)
117 }
118
119 pub fn key_mods(&mut self, key: i32, mods: i32) -> bool {
120 self.event(&format!("key:{key}:{mods}"))
121 }
122
123 pub fn type_text(&mut self, text: &str) -> &mut Self {
125 for c in text.chars() {
126 self.event(&format!("char:{}", c as u32));
127 }
128 self
129 }
130
131 pub fn type_into(&mut self, id: &str, text: &str) -> &mut Self {
133 self.click_id(id);
134 self.key(keys::END);
135 self.type_text(text)
136 }
137
138 pub fn vm(&self) -> &W::Vm {
139 &self.host.vm
140 }
141
142 pub fn vm_mut(&mut self) -> &mut W::Vm {
145 self.host.dirty = true;
146 &mut self.host.vm
147 }
148
149 pub fn handle(&self) -> ScreenHandle<W::Vm> {
151 self.host.handle.clone()
152 }
153
154 pub fn root(&self) -> &Widget {
156 self.host.content()
157 }
158
159 pub fn ui(&self) -> &UiRoot {
161 &self.host.ui
162 }
163
164 pub fn path(&self, id: &str) -> Vec<u16> {
166 find_path(self.root(), id).unwrap_or_else(|| panic!("yog-kit test: no widget with id {id:?}"))
167 }
168
169 pub fn widget(&self, id: &str) -> Option<&Widget> {
170 find_path(self.root(), id).and_then(|p| self.root().get_path(&p))
171 }
172
173 pub fn text_at(&self, path: &[u16]) -> Option<String> {
175 match &self.root().get_path(path)?.kind {
176 WidgetKind::Label(t) | WidgetKind::Button(t) => Some(t.clone()),
177 WidgetKind::TextBox(tb) => Some(tb.text.clone()),
178 _ => None,
179 }
180 }
181
182 pub fn text(&self, id: &str) -> Option<String> {
184 self.text_at(&find_path(self.root(), id)?)
185 }
186
187 pub fn is_visible(&self, id: &str) -> bool {
189 let Some(p) = find_path(self.root(), id) else {
190 return false;
191 };
192 (0..=p.len()).all(|n| self.root().get_path(&p[..n]).is_some_and(|w| w.visible))
193 }
194
195 pub fn state(&self, id: &str) -> StateFlags {
196 self.widget(id).map_or(StateFlags::NONE, |w| w.state)
197 }
198
199 pub fn node(&self, id: &str) -> Option<&LayoutNode> {
200 self.host.content_node(&find_path(self.root(), id)?)
201 }
202
203 pub fn rect(&self, id: &str) -> Option<Rect> {
204 self.node(id).map(|n| n.rect)
205 }
206
207 pub fn scroll_offset(&self, i: usize) -> f32 {
209 self.host.parts.scrollers[i].state.offset
210 }
211
212 pub fn requests(&self) -> &[String] {
214 &self.host.log
215 }
216
217 pub fn closed(&self) -> bool {
219 self.host.log.iter().any(|r| r == "close")
220 }
221}