1use crate::templates::{FuncTemplate, IDataTemplate, TemplateRef};
2use crate::Control;
3use ferroui_base::data::BindingPriority;
4use ferroui_base::layout::Layoutable;
5use ferroui_base::styling::IStyle;
6use ferroui_base::{
7 ferro_property, AttachedProperty, BoxedValue, FerroObject, FerroObjectExtensions, FerroProperty, Ref, StyledElement, Visual,
8};
9use std::cell::{Cell, RefCell};
10use std::collections::HashMap;
11use std::rc::Rc;
12
13pub type PreviewTemplate = TemplateRef<Option<Ref<Control>>>;
15
16#[allow(dead_code)]
20enum Target {
21 Object(Ref<FerroObject>),
22 DataTemplate(Rc<dyn IDataTemplate>),
23 Style(Rc<dyn IStyle>),
24}
25
26fn object_key(target: &FerroObject) -> usize {
27 target as *const FerroObject as usize
28}
29
30fn data_template_key(target: &Rc<dyn IDataTemplate>) -> usize {
31 Rc::as_ptr(target) as *const () as usize
32}
33
34fn style_key(target: &Rc<dyn IStyle>) -> usize {
36 match target.as_object() {
37 Some(object) => object_key(object),
38 None => Rc::as_ptr(target) as *const () as usize,
39 }
40}
41
42ferroui_base::ferro_thread_local! {
43 static IS_DESIGN_MODE: Cell<bool> = const { Cell::new(false) };
44 static PREVIEW_WITH: RefCell<HashMap<usize, (Target, Option<PreviewTemplate>)>> = RefCell::new(HashMap::new());
45 static TEMPLATE_DATA_CONTEXT: RefCell<HashMap<usize, (Rc<dyn IDataTemplate>, Option<BoxedValue>)>> =
46 RefCell::new(HashMap::new());
47}
48
49fn set_preview(key: usize, target: Target, template: Option<PreviewTemplate>) {
50 let previous = PREVIEW_WITH.with(|map| map.borrow_mut().insert(key, (target, template)));
51 drop(previous);
52}
53
54fn get_preview(key: usize) -> Option<Ref<Control>> {
55 let template = PREVIEW_WITH.with(|map| map.borrow().get(&key).and_then(|(_, template)| template.clone()));
56 template.and_then(|template| template.build_typed())
57}
58
59fn control_template(control: Ref<Control>) -> PreviewTemplate {
60 FuncTemplate::new(move || Some(control.clone()))
61}
62
63fn build_template(template: PreviewTemplate) -> PreviewTemplate {
64 FuncTemplate::new(move || template.build_typed())
65}
66
67pub struct Design;
69
70ferroui_base::ferro_static_type!(Design);
71
72ferroui_base::ferro_properties! { impl Design, also [
73 Design::height_property,
74 Design::width_property,
75 Design::data_context_property,
76 Design::preview_with_property,
77 Design::design_style_property,
78] {} }
79
80impl Design {
81 pub fn is_design_mode() -> bool {
86 IS_DESIGN_MODE.get()
87 }
88
89 #[doc(hidden)]
92 pub fn set_is_design_mode(value: bool) {
93 IS_DESIGN_MODE.set(value)
94 }
95
96 ferro_property!(for Design;
97 pub fn height_property() -> AttachedProperty<f64> {
100 FerroProperty::register_attached::<Design, Control, _>("Height", 0.0)
101 }
102 );
103
104 pub fn set_height(control: &Control, value: f64) {
106 control.set_value(Self::height_property(), value)
107 }
108
109 pub fn get_height(control: &Control) -> f64 {
111 control.get_value(Self::height_property())
112 }
113
114 ferro_property!(for Design;
115 pub fn width_property() -> AttachedProperty<f64> {
118 FerroProperty::register_attached::<Design, Control, _>("Width", 0.0)
119 }
120 );
121
122 pub fn set_width(control: &Control, value: f64) {
124 control.set_value(Self::width_property(), value)
125 }
126
127 pub fn get_width(control: &Control) -> f64 {
129 control.get_value(Self::width_property())
130 }
131
132 ferro_property!(for Design;
133 pub fn data_context_property() -> AttachedProperty<Option<BoxedValue>> {
136 FerroProperty::register_attached::<Design, Control, _>("DataContext", None)
137 }
138 );
139
140 pub fn set_data_context(control: &Control, value: Option<BoxedValue>) {
142 control.set_value(Self::data_context_property(), value)
143 }
144
145 pub fn get_data_context(control: &Control) -> Option<BoxedValue> {
147 control.get_value(Self::data_context_property())
148 }
149
150 pub fn set_data_context_for_data_template(control: &Rc<dyn IDataTemplate>, value: Option<BoxedValue>) {
152 let previous = TEMPLATE_DATA_CONTEXT
153 .with(|map| map.borrow_mut().insert(data_template_key(control), (control.clone(), value)));
154 drop(previous);
155 }
156
157 pub fn get_data_context_for_data_template(control: &Rc<dyn IDataTemplate>) -> Option<BoxedValue> {
159 TEMPLATE_DATA_CONTEXT
160 .with(|map| map.borrow().get(&data_template_key(control)).and_then(|(_, value)| value.clone()))
161 }
162
163 ferro_property!(for Design;
164 pub fn preview_with_property() -> AttachedProperty<Option<Ref<Control>>> {
167 FerroProperty::register_attached::<Design, FerroObject, _>("PreviewWith", None)
168 }
169 );
170
171 pub fn set_preview_with_template(target: &Ref<FerroObject>, template: Option<PreviewTemplate>) {
176 set_preview(object_key(target), Target::Object(target.clone()), template)
177 }
178
179 pub fn set_preview_with(target: &Ref<FerroObject>, control: Option<Ref<Control>>) {
185 let template = match control {
186 Some(_) if target.is::<Visual>() => None,
188 Some(control) => Some(control_template(control)),
189 None => None,
190 };
191 set_preview(object_key(target), Target::Object(target.clone()), template)
192 }
193
194 pub fn set_preview_with_template_for_data_template(
197 target: &Rc<dyn IDataTemplate>,
198 template: Option<PreviewTemplate>,
199 ) {
200 set_preview(data_template_key(target), Target::DataTemplate(target.clone()), template.map(build_template))
201 }
202
203 pub fn set_preview_with_for_data_template(target: &Rc<dyn IDataTemplate>, control: Option<Ref<Control>>) {
206 set_preview(data_template_key(target), Target::DataTemplate(target.clone()), control.map(control_template))
207 }
208
209 pub fn set_preview_with_template_for_style(target: &Rc<dyn IStyle>, template: Option<PreviewTemplate>) {
211 set_preview(style_key(target), Target::Style(target.clone()), template.map(build_template))
212 }
213
214 pub fn set_preview_with_for_style(target: &Rc<dyn IStyle>, control: Option<Ref<Control>>) {
216 set_preview(style_key(target), Target::Style(target.clone()), control.map(control_template))
217 }
218
219 pub fn get_preview_with(target: &FerroObject) -> Option<Ref<Control>> {
222 get_preview(object_key(target))
223 }
224
225 pub fn get_preview_with_for_data_template(target: &Rc<dyn IDataTemplate>) -> Option<Ref<Control>> {
228 get_preview(data_template_key(target))
229 }
230
231 pub fn get_preview_with_for_style(target: &Rc<dyn IStyle>) -> Option<Ref<Control>> {
234 get_preview(style_key(target))
235 }
236
237 ferro_property!(for Design;
238 pub fn design_style_property() -> AttachedProperty<Option<DesignStyleValue>> {
244 FerroProperty::register_attached::<Design, Control, _>("DesignStyle", None)
245 }
246 );
247
248 pub fn set_design_style(control: &Control, value: Rc<dyn IStyle>) {
250 control.set_value(Self::design_style_property(), Some(DesignStyleValue(value)))
251 }
252
253 pub fn get_design_style(control: &Control) -> Option<Rc<dyn IStyle>> {
255 control.get_value(Self::design_style_property()).map(|value| value.0)
256 }
257
258 pub fn apply_design_mode_properties(target: &Control, source: &Control) {
260 let source_object: &FerroObject = source;
261 if source.is_set(Self::width_property()) {
262 target.bind(
263 Layoutable::width_property(),
264 FerroObjectExtensions::get_observable(source_object, &**Self::width_property()),
265 BindingPriority::LocalValue,
266 );
267 }
268 if source.is_set(Self::height_property()) {
269 target.bind(
270 Layoutable::height_property(),
271 FerroObjectExtensions::get_observable(source_object, &**Self::height_property()),
272 BindingPriority::LocalValue,
273 );
274 }
275 if source.is_set(Self::data_context_property()) {
276 target.bind(
277 StyledElement::data_context_property(),
278 FerroObjectExtensions::get_observable(source_object, &**Self::data_context_property()),
279 BindingPriority::LocalValue,
280 );
281 }
282 if source.is_set(Self::design_style_property()) {
283 if let Some(style) = Self::get_design_style(source) {
284 target.styles().add(style);
285 }
286 }
287 }
288
289 #[doc(hidden)]
294 pub fn create_preview_with_control(target: &PreviewTarget) -> Option<Ref<Control>> {
295 let target = match target {
296 PreviewTarget::Style(style) => return Some(Self::create_preview_for_style(style)),
297 PreviewTarget::DataTemplate(template) => return Some(Self::create_preview_for_data_template(template)),
298 PreviewTarget::Other => return message_line("This file cannot be previewed in design view"),
299 PreviewTarget::Object(target) => target,
300 };
301
302 if let Some(style) = target.cast::<ferroui_base::styling::StyleBase>() {
304 return Some(Self::create_preview_for_style(&style.into()));
305 }
306 if let Some(styles) = target.cast::<ferroui_base::styling::Styles>() {
307 return Some(Self::create_preview_for_style(&styles.into()));
308 }
309
310 if let Some(resources) = target.cast::<ferroui_base::controls::ResourceDictionary>() {
311 if let Some(substitute) = Self::get_preview_with(target) {
312 substitute.resources().merged_dictionaries().add(resources.into());
313 return Some(substitute);
314 }
315
316 return Some(message(&[
317 "ResourceDictionaries can't be previewed without Design.PreviewWith. Add",
318 "<Design.PreviewWith>",
319 " <Border Padding=\"20\"><!-- YOUR CONTROL FOR PREVIEW HERE --></Border>",
320 "</Design.PreviewWith>",
321 "in your resource dictionary",
322 ]));
323 }
324
325 if target.is::<crate::Application>() {
326 return message_line("This file cannot be previewed in design view");
327 }
328
329 if !target.is::<crate::Window>() {
330 if let Some(preview_with) = Self::get_preview_with(target) {
331 return Some(preview_with);
332 }
333 }
334
335 match target.cast::<Control>() {
336 Some(control) => Some(control),
337 None => message_line("This file cannot be previewed in design view"),
338 }
339 }
340
341 fn create_preview_for_style(style: &Rc<dyn IStyle>) -> Ref<Control> {
342 if let Some(substitute) = Self::get_preview_with_for_style(style) {
343 substitute.styles().add(style.clone());
344 return substitute;
345 }
346
347 message(&[
348 "Styles can't be previewed without Design.PreviewWith. Add",
349 "<Design.PreviewWith>",
350 " <Border Padding=\"20\"><!-- YOUR CONTROL FOR PREVIEW HERE --></Border>",
351 "</Design.PreviewWith>",
352 "before setters in your first Style",
353 ])
354 }
355
356 fn create_preview_for_data_template(template: &Rc<dyn IDataTemplate>) -> Ref<Control> {
357 let substitute =
358 Self::get_preview_with_for_data_template(template).and_then(|control| control.cast::<crate::ContentControl>());
359 if let Some(substitute) = substitute {
360 substitute.set_content_template(Some(template.clone()));
361 if !substitute.is_set(Self::data_context_property())
362 && substitute.is_set(StyledElement::data_context_property())
363 {
364 let data_context = substitute.get_value(StyledElement::data_context_property());
365 substitute.set_data_context(data_context);
366 }
367 return substitute.upcast();
368 }
369
370 if let Some(data_context) = Self::get_data_context_for_data_template(template) {
371 let substitute = crate::ContentControl::new();
372 substitute.set_content_template(Some(template.clone()));
373 substitute.set_data_context(Some(data_context.clone()));
374 substitute.set_content(Some(data_context));
375 return substitute.upcast();
376 }
377
378 message(&[
379 "IDataTemplate can't be previewed without Design.PreviewWith.",
380 "Provide ContentControl with your design data as Content. Previewer will set ContentTemplate from this file.",
381 "<Design.PreviewWith>",
382 " <ContentControl Content=\"{x:Static YOUR_DATA_OBJECT_HERE}\" />",
383 "</Design.PreviewWith>",
384 ])
385 }
386}
387
388pub enum PreviewTarget {
391 Style(Rc<dyn IStyle>),
393 DataTemplate(Rc<dyn IDataTemplate>),
395 Object(Ref<FerroObject>),
398 Other,
400}
401
402fn message_line(text: &str) -> Option<Ref<Control>> {
404 let line = crate::TextBlock::new();
405 line.set_text(Some(text));
406 Some(line.upcast())
407}
408
409fn message(lines: &[&str]) -> Ref<Control> {
411 let panel = crate::StackPanel::new();
412 for line in lines {
413 if let Some(line) = message_line(line) {
414 panel.children().add(line);
415 }
416 }
417 panel.upcast()
418}
419
420#[derive(Clone)]
422pub struct DesignStyleValue(pub Rc<dyn IStyle>);
423
424impl PartialEq for DesignStyleValue {
425 fn eq(&self, other: &Self) -> bool {
426 ferroui_base::styling::style_ptr_eq(&self.0, &other.0)
427 }
428}