1use std::ffi::c_void;
13use std::panic::{AssertUnwindSafe, catch_unwind};
14
15use yog_abi::{YogBytes, YogGfxApi};
16use yog_data::Schema;
17use yog_gfx::GfxContext;
18
19use super::{COMMON_ATTRS, PropKind, Props, apply_style, components, find};
20use crate::widget::{self, Widget};
21use crate::{FlexDir, UiRoot, Units};
22
23pub const ABI: u32 = 2;
25
26pub type WriteFn = unsafe extern "C" fn(user_data: *mut c_void, reply: YogBytes);
28
29#[derive(Debug, Clone, PartialEq, Schema)]
31#[data(tag = "request")]
32pub enum Request {
33 Describe,
34 Render(Render),
35}
36
37#[derive(Debug, Clone, PartialEq, Schema)]
39pub struct Render {
40 pub name: String,
41 #[data(default)]
43 pub attrs: Vec<(String, String)>,
44 pub w: f32,
46 pub h: f32,
47 pub avail_w: f32,
49 pub avail_h: f32,
50}
51
52#[derive(Debug, Clone, PartialEq, Schema)]
54#[data(tag = "answer")]
55pub enum Answer {
56 Described { components: Vec<ComponentInfo> },
57 Rendered { w: f32, h: f32 },
58}
59
60pub type Reply = Result<Answer, String>;
62
63#[derive(Debug, Clone, PartialEq, Schema)]
65pub struct ComponentInfo {
66 pub name: String,
67 #[data(default)]
69 pub description: String,
70 #[data(default)]
72 pub display_name: String,
73 #[data(default)]
75 pub icon: String,
76 #[data(default)]
78 pub category: String,
79 #[data(default)]
81 pub design_w: f32,
82 #[data(default)]
83 pub design_h: f32,
84 #[data(default = "yes")]
86 pub toolbox: bool,
87 #[data(default)]
88 pub props: Vec<PropInfo>,
89 #[data(default)]
91 pub file: Option<String>,
92}
93
94#[derive(Debug, Clone, PartialEq, Schema)]
96pub struct PropInfo {
97 pub name: String,
98 pub kind: String,
100 #[data(default)]
101 pub default: String,
102 #[data(default)]
103 pub doc: String,
104 #[data(default)]
106 pub category: String,
107 #[data(default)]
108 pub options: Vec<String>,
109}
110
111fn yes() -> bool {
112 true
113}
114
115pub unsafe fn call(
121 request: YogBytes,
122 gfx: *const YogGfxApi,
123 user_data: *mut c_void,
124 write: WriteFn,
125) -> bool {
126 let request = unsafe { request.as_slice() };
128 let reply: Reply = match catch_unwind(AssertUnwindSafe(|| answer(request, gfx))) {
129 Ok(reply) => reply,
130 Err(panic) => Err(panic_message(&*panic)),
131 };
132 let bytes = yog_data::to_bytes(&reply);
133 unsafe { write(user_data, YogBytes::new(&bytes)) };
135 reply.is_ok()
136}
137
138fn panic_message(panic: &(dyn std::any::Any + Send)) -> String {
139 let msg = panic
140 .downcast_ref::<&str>()
141 .map(|s| s.to_string())
142 .or_else(|| panic.downcast_ref::<String>().cloned())
143 .unwrap_or_else(|| "unknown panic".into());
144 format!("the component panicked: {msg}")
145}
146
147fn answer(request: &[u8], gfx: *const YogGfxApi) -> Reply {
148 let request = yog_data::read_bytes::<Request>(request)
149 .map_err(|e| format!("not a designer request: {e:?}"))?;
150 match request {
151 Request::Describe => Ok(describe()),
152 Request::Render(r) => {
153 if gfx.is_null() {
154 return Err("render: no gfx table".into());
155 }
156 let ctx = unsafe { GfxContext::from_raw(gfx) };
158 render(&ctx, &r)
159 }
160 }
161}
162
163fn render(ctx: &GfxContext, r: &Render) -> Reply {
164 let spec = find(&r.name).ok_or_else(|| format!("no component named {}", r.name))?;
165 let (w, h, aw, ah) = (r.w, r.h, r.avail_w, r.avail_h);
166 type Attrs<'a> = Vec<(&'a str, &'a str)>;
167 let (common, own): (Attrs, Attrs) = r
168 .attrs
169 .iter()
170 .map(|(k, v)| (k.as_str(), v.as_str()))
171 .partition(|(k, _)| COMMON_ATTRS.contains(k));
172 let mut root: Widget = (spec.build)(&Props::new(spec.props, &own));
173 for (k, v) in common {
174 root = apply_style(root, k, v);
175 }
176 if w > 0.0 {
177 root = root.w(Units::Px(w));
178 }
179 if h > 0.0 {
180 root = root.h(Units::Px(h));
181 }
182 let (aw, ah) = (aw.max(1.0), ah.max(1.0));
183 let screen = widget::panel(FlexDir::Column)
184 .w(Units::Px(aw))
185 .h(Units::Px(ah))
186 .child(root.at(0.0, 0.0));
187 crate::text::use_host_metrics(ctx);
189 crate::image::use_host_images(ctx);
190 let mut ui = UiRoot::new(yog_ids::yog::DESIGNER_SCREEN, screen);
191 ui.layout(aw, ah);
192 let rect = ui
193 .layout_root
194 .children
195 .first()
196 .map(|n| n.rect)
197 .unwrap_or_default();
198 ui.render(ctx);
199 Ok(Answer::Rendered {
200 w: rect.w,
201 h: rect.h,
202 })
203}
204
205fn describe() -> Answer {
206 let components = components()
207 .iter()
208 .map(|c| ComponentInfo {
209 name: c.name.into(),
210 description: c.doc.into(),
211 display_name: c.display_name.into(),
212 icon: c.icon.into(),
213 category: c.category.into(),
214 design_w: c.design_size.0,
215 design_h: c.design_size.1,
216 toolbox: c.toolbox,
217 props: c
218 .props
219 .iter()
220 .map(|p| PropInfo {
221 name: p.name.into(),
222 kind: p.kind.as_str().into(),
223 default: p.default.into(),
224 doc: p.doc.into(),
225 category: p.category.into(),
226 options: match p.kind {
227 PropKind::Choice(o) => o.iter().map(|s| s.to_string()).collect(),
228 _ => Vec::new(),
229 },
230 })
231 .collect(),
232 file: None,
233 })
234 .collect();
235 Answer::Described { components }
236}