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 scroll(&mut self, dy: f32) -> bool {
94 self.event(&format!("scroll:{dy}"))
95 }
96
97 pub fn key(&mut self, key: i32) -> bool {
99 self.key_mods(key, 0)
100 }
101
102 pub fn key_mods(&mut self, key: i32, mods: i32) -> bool {
103 self.event(&format!("key:{key}:{mods}"))
104 }
105
106 pub fn type_text(&mut self, text: &str) -> &mut Self {
108 for c in text.chars() {
109 self.event(&format!("char:{}", c as u32));
110 }
111 self
112 }
113
114 pub fn type_into(&mut self, id: &str, text: &str) -> &mut Self {
116 self.click_id(id);
117 self.key(keys::END);
118 self.type_text(text)
119 }
120
121 pub fn vm(&self) -> &W::Vm {
122 &self.host.vm
123 }
124
125 pub fn vm_mut(&mut self) -> &mut W::Vm {
128 self.host.dirty = true;
129 &mut self.host.vm
130 }
131
132 pub fn handle(&self) -> ScreenHandle<W::Vm> {
134 self.host.handle.clone()
135 }
136
137 pub fn root(&self) -> &Widget {
139 self.host.content()
140 }
141
142 pub fn ui(&self) -> &UiRoot {
144 &self.host.ui
145 }
146
147 pub fn path(&self, id: &str) -> Vec<u16> {
149 find_path(self.root(), id).unwrap_or_else(|| panic!("yog-kit test: no widget with id {id:?}"))
150 }
151
152 pub fn widget(&self, id: &str) -> Option<&Widget> {
153 find_path(self.root(), id).and_then(|p| self.root().get_path(&p))
154 }
155
156 pub fn text_at(&self, path: &[u16]) -> Option<String> {
158 match &self.root().get_path(path)?.kind {
159 WidgetKind::Label(t) | WidgetKind::Button(t) => Some(t.clone()),
160 WidgetKind::TextBox(tb) => Some(tb.text.clone()),
161 _ => None,
162 }
163 }
164
165 pub fn text(&self, id: &str) -> Option<String> {
167 self.text_at(&find_path(self.root(), id)?)
168 }
169
170 pub fn is_visible(&self, id: &str) -> bool {
172 let Some(p) = find_path(self.root(), id) else {
173 return false;
174 };
175 (0..=p.len()).all(|n| self.root().get_path(&p[..n]).is_some_and(|w| w.visible))
176 }
177
178 pub fn state(&self, id: &str) -> StateFlags {
179 self.widget(id).map_or(StateFlags::NONE, |w| w.state)
180 }
181
182 pub fn node(&self, id: &str) -> Option<&LayoutNode> {
183 self.host.content_node(&find_path(self.root(), id)?)
184 }
185
186 pub fn rect(&self, id: &str) -> Option<Rect> {
187 self.node(id).map(|n| n.rect)
188 }
189
190 pub fn scroll_offset(&self, i: usize) -> f32 {
192 self.host.parts.scrollers[i].state.offset
193 }
194
195 pub fn requests(&self) -> &[String] {
197 &self.host.log
198 }
199
200 pub fn closed(&self) -> bool {
202 self.host.log.iter().any(|r| r == "close")
203 }
204}