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