1use crate::{
2 border::BorderBuilder,
3 check_box::CheckBoxBuilder,
4 core::{
5 algebra::Vector2,
6 inspect::{CastError, Inspect, PropertyValue},
7 pool::Handle,
8 },
9 define_constructor,
10 expander::ExpanderBuilder,
11 formatted_text::WrapMode,
12 grid::{Column, GridBuilder, Row},
13 inspector::editors::{
14 PropertyEditorBuildContext, PropertyEditorDefinition, PropertyEditorDefinitionContainer,
15 PropertyEditorInstance, PropertyEditorMessageContext,
16 },
17 message::{MessageDirection, UiMessage},
18 stack_panel::StackPanelBuilder,
19 text::TextBuilder,
20 utils::{make_arrow, make_simple_tooltip, ArrowDirection},
21 widget::{Widget, WidgetBuilder, WidgetMessage},
22 BuildContext, Control, Thickness, UiNode, UserInterface, VerticalAlignment,
23};
24use std::{
25 any::{Any, TypeId},
26 fmt::{Debug, Formatter},
27 ops::{Deref, DerefMut},
28 rc::Rc,
29};
30
31pub mod editors;
32
33#[derive(Debug, Clone, PartialEq)]
34pub enum CollectionChanged {
35 Add,
37 Remove(usize),
39 ItemChanged {
41 index: usize,
43 property: PropertyChanged,
44 },
45}
46
47impl CollectionChanged {
48 define_constructor!(CollectionChanged:Add => fn add(), layout: false);
49 define_constructor!(CollectionChanged:Remove => fn remove(usize), layout: false);
50 define_constructor!(CollectionChanged:ItemChanged => fn item_changed(index: usize, property: PropertyChanged), layout: false);
51}
52
53#[derive(Debug, Clone)]
54pub enum FieldKind {
55 Collection(Box<CollectionChanged>),
56 Inspectable(Box<PropertyChanged>),
57 Object(ObjectValue),
58}
59
60#[derive(Debug, Clone)]
61pub struct ObjectValue {
62 value: Rc<dyn PropertyValue>,
63}
64
65impl PartialEq for ObjectValue {
66 fn eq(&self, other: &Self) -> bool {
67 let ptr_a = &*self.value as *const _ as *const ();
69 let ptr_b = &*other.value as *const _ as *const ();
70 std::ptr::eq(ptr_a, ptr_b)
72 }
73}
74
75impl ObjectValue {
76 pub fn cast_value<T: 'static>(&self) -> Option<&T> {
77 (*self.value).as_any().downcast_ref::<T>()
78 }
79
80 pub fn cast_clone<T: Clone + 'static>(&self) -> Option<T> {
81 (*self.value).as_any().downcast_ref::<T>().cloned()
82 }
83}
84
85impl PartialEq for FieldKind {
86 fn eq(&self, other: &Self) -> bool {
87 match (self, other) {
88 (FieldKind::Collection(l), FieldKind::Collection(r)) => std::ptr::eq(&**l, &**r),
89 (FieldKind::Inspectable(l), FieldKind::Inspectable(r)) => std::ptr::eq(&**l, &**r),
90 (FieldKind::Object(l), FieldKind::Object(r)) => l == r,
91 _ => false,
92 }
93 }
94}
95
96impl FieldKind {
97 pub fn object<T: PropertyValue>(value: T) -> Self {
98 Self::Object(ObjectValue {
99 value: Rc::new(value),
100 })
101 }
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub struct PropertyChanged {
106 pub name: String,
107 pub owner_type_id: TypeId,
108 pub value: FieldKind,
109}
110
111impl PropertyChanged {
112 pub fn path(&self) -> String {
113 let mut path = self.name.clone();
114 match self.value {
115 FieldKind::Collection(ref collection_changed) => {
116 if let CollectionChanged::ItemChanged {
117 ref property,
118 index,
119 } = **collection_changed
120 {
121 path += format!("[{}].{}", index, property.path()).as_ref();
122 }
123 }
124 FieldKind::Inspectable(ref inspectable) => {
125 path += format!(".{}", inspectable.path()).as_ref();
126 }
127 FieldKind::Object(_) => {}
128 }
129 path
130 }
131}
132
133#[derive(Debug, Clone, PartialEq)]
134pub enum InspectorMessage {
135 Context(InspectorContext),
136 PropertyChanged(PropertyChanged),
137}
138
139impl InspectorMessage {
140 define_constructor!(InspectorMessage:Context => fn context(InspectorContext), layout: false);
141 define_constructor!(InspectorMessage:PropertyChanged => fn property_changed(PropertyChanged), layout: false);
142}
143
144pub trait InspectorEnvironment: Any + Send + Sync {
145 fn as_any(&self) -> &dyn Any;
146}
147
148#[derive(Clone)]
149pub struct Inspector {
150 widget: Widget,
151 context: InspectorContext,
152}
153
154crate::define_widget_deref!(Inspector);
155
156impl Inspector {
157 pub fn context(&self) -> &InspectorContext {
158 &self.context
159 }
160}
161
162pub const NAME_COLUMN_WIDTH: f32 = 150.0;
163pub const HEADER_MARGIN: Thickness = Thickness {
164 left: 2.0,
165 top: 1.0,
166 right: 4.0,
167 bottom: 1.0,
168};
169
170#[derive(Debug)]
171pub enum InspectorError {
172 CastError(CastError),
173 OutOfSync,
174 Custom(String),
175 Group(Vec<InspectorError>),
176}
177
178impl From<CastError> for InspectorError {
179 fn from(e: CastError) -> Self {
180 Self::CastError(e)
181 }
182}
183
184#[derive(Clone, Debug)]
185pub struct ContextEntry {
186 pub property_name: String,
187 pub property_owner_type_id: TypeId,
188 pub property_editor_definition: Rc<dyn PropertyEditorDefinition>,
189 pub property_editor: Handle<UiNode>,
190}
191
192impl PartialEq for ContextEntry {
193 fn eq(&self, other: &Self) -> bool {
194 let ptr_a = &*self.property_editor_definition as *const _ as *const ();
196 let ptr_b = &*other.property_editor_definition as *const _ as *const ();
197
198 self.property_editor == other.property_editor
199 && self.property_name == other.property_name
200 && std::ptr::eq(ptr_a, ptr_b)
202 }
203}
204
205#[derive(Clone)]
206pub struct InspectorContext {
207 stack_panel: Handle<UiNode>,
208 entries: Vec<ContextEntry>,
209 property_definitions: Rc<PropertyEditorDefinitionContainer>,
210 environment: Option<Rc<dyn InspectorEnvironment>>,
211 sync_flag: u64,
212}
213
214impl PartialEq for InspectorContext {
215 fn eq(&self, other: &Self) -> bool {
216 self.entries == other.entries
217 }
218}
219
220impl Default for InspectorContext {
221 fn default() -> Self {
222 Self {
223 stack_panel: Default::default(),
224 entries: Default::default(),
225 property_definitions: Rc::new(PropertyEditorDefinitionContainer::new()),
226 environment: None,
227 sync_flag: 0,
228 }
229 }
230}
231
232impl Debug for InspectorContext {
233 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
234 writeln!(f, "InspectorContext")
235 }
236}
237
238pub fn make_property_margin(layer_index: usize) -> Thickness {
239 let mut margin = HEADER_MARGIN;
240 margin.left += 10.0 + layer_index as f32 * 10.0;
241 margin
242}
243
244fn make_expander_margin(layer_index: usize) -> Thickness {
245 let mut margin = HEADER_MARGIN;
246 margin.left += layer_index as f32 * 10.0;
247 margin
248}
249
250fn make_expander_check_box(
251 layer_index: usize,
252 property_name: &str,
253 ctx: &mut BuildContext,
254) -> Handle<UiNode> {
255 CheckBoxBuilder::new(
256 WidgetBuilder::new()
257 .with_vertical_alignment(VerticalAlignment::Center)
258 .with_margin(make_expander_margin(layer_index)),
259 )
260 .with_background(
261 BorderBuilder::new(
262 WidgetBuilder::new()
263 .with_vertical_alignment(VerticalAlignment::Center)
264 .with_min_size(Vector2::new(4.0, 4.0)),
265 )
266 .with_stroke_thickness(Thickness::zero())
267 .build(ctx),
268 )
269 .with_content(
270 TextBuilder::new(
271 WidgetBuilder::new()
272 .with_height(16.0)
273 .with_margin(Thickness::left(2.0)),
274 )
275 .with_vertical_text_alignment(VerticalAlignment::Center)
276 .with_text(property_name)
277 .build(ctx),
278 )
279 .checked(Some(true))
280 .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
281 .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
282 .build(ctx)
283}
284
285pub fn make_expander_container(
286 layer_index: usize,
287 property_name: &str,
288 header: Handle<UiNode>,
289 content: Handle<UiNode>,
290 ctx: &mut BuildContext,
291) -> Handle<UiNode> {
292 ExpanderBuilder::new(WidgetBuilder::new())
293 .with_checkbox(make_expander_check_box(layer_index, property_name, ctx))
294 .with_expander_column(Column::strict(NAME_COLUMN_WIDTH))
295 .with_expanded(true)
296 .with_header(header)
297 .with_content(content)
298 .build(ctx)
299}
300
301fn create_header(ctx: &mut BuildContext, text: &str, layer_index: usize) -> Handle<UiNode> {
302 TextBuilder::new(WidgetBuilder::new().with_margin(make_property_margin(layer_index)))
303 .with_text(text)
304 .with_vertical_text_alignment(VerticalAlignment::Center)
305 .build(ctx)
306}
307
308fn make_tooltip(ctx: &mut BuildContext, text: &str) -> Handle<UiNode> {
309 if text.is_empty() {
310 Handle::NONE
311 } else {
312 make_simple_tooltip(ctx, text)
313 }
314}
315
316fn make_simple_property_container(
317 title: Handle<UiNode>,
318 editor: Handle<UiNode>,
319 description: &str,
320 ctx: &mut BuildContext,
321) -> Handle<UiNode> {
322 ctx[editor].set_row(0).set_column(1);
323
324 let tooltip = make_tooltip(ctx, description);
325 ctx[title].set_tooltip(tooltip);
326
327 GridBuilder::new(WidgetBuilder::new().with_child(title).with_child(editor))
328 .add_rows(vec![Row::strict(26.0)])
329 .add_columns(vec![Column::strict(NAME_COLUMN_WIDTH), Column::stretch()])
330 .build(ctx)
331}
332
333impl InspectorContext {
334 pub fn from_object(
335 object: &dyn Inspect,
336 ctx: &mut BuildContext,
337 definition_container: Rc<PropertyEditorDefinitionContainer>,
338 environment: Option<Rc<dyn InspectorEnvironment>>,
339 sync_flag: u64,
340 layer_index: usize,
341 ) -> Self {
342 let mut entries = Vec::new();
343
344 let editors = object
345 .properties()
346 .iter()
347 .enumerate()
348 .map(|(i, info)| {
349 let description = if info.description.is_empty() {
350 info.display_name.to_string()
351 } else {
352 format!("{}\n\n{}", info.display_name, info.description)
353 };
354
355 if let Some(definition) = definition_container
356 .definitions()
357 .get(&info.value.type_id())
358 {
359 match definition.create_instance(PropertyEditorBuildContext {
360 build_context: ctx,
361 property_info: info,
362 environment: environment.clone(),
363 definition_container: definition_container.clone(),
364 sync_flag,
365 layer_index,
366 }) {
367 Ok(instance) => {
368 let (container, editor) = match instance {
369 PropertyEditorInstance::Simple { editor } => (
370 make_simple_property_container(
371 create_header(ctx, info.display_name, layer_index),
372 editor,
373 &description,
374 ctx,
375 ),
376 editor,
377 ),
378 PropertyEditorInstance::Custom { container, editor } => {
379 (container, editor)
380 }
381 };
382
383 entries.push(ContextEntry {
384 property_editor: editor,
385 property_editor_definition: definition.clone(),
386 property_name: info.name.to_string(),
387 property_owner_type_id: info.owner_type_id,
388 });
389
390 if info.read_only {
391 ctx[editor].set_enabled(false);
392 }
393
394 container
395 }
396 Err(e) => make_simple_property_container(
397 create_header(ctx, info.display_name, layer_index),
398 TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
399 .with_wrap(WrapMode::Word)
400 .with_vertical_text_alignment(VerticalAlignment::Center)
401 .with_text(format!(
402 "Unable to create property \
403 editor instance: Reason {:?}",
404 e
405 ))
406 .build(ctx),
407 &description,
408 ctx,
409 ),
410 }
411 } else {
412 make_simple_property_container(
413 create_header(ctx, info.display_name, layer_index),
414 TextBuilder::new(WidgetBuilder::new().on_row(i).on_column(1))
415 .with_wrap(WrapMode::Word)
416 .with_vertical_text_alignment(VerticalAlignment::Center)
417 .with_text("Property Editor Is Missing!")
418 .build(ctx),
419 &description,
420 ctx,
421 )
422 }
423 })
424 .collect::<Vec<_>>();
425
426 let stack_panel =
427 StackPanelBuilder::new(WidgetBuilder::new().with_children(editors)).build(ctx);
428
429 Self {
430 stack_panel,
431 entries,
432 property_definitions: definition_container,
433 sync_flag,
434 environment,
435 }
436 }
437
438 pub fn sync(
439 &self,
440 object: &dyn Inspect,
441 ui: &mut UserInterface,
442 layer_index: usize,
443 ) -> Result<(), Vec<InspectorError>> {
444 let mut sync_errors = Vec::new();
445
446 for info in object.properties() {
447 if let Some(constructor) = self
448 .property_definitions
449 .definitions()
450 .get(&info.value.type_id())
451 {
452 let ctx = PropertyEditorMessageContext {
453 sync_flag: self.sync_flag,
454 instance: self.find_property_editor(info.name),
455 ui,
456 property_info: &info,
457 definition_container: self.property_definitions.clone(),
458 layer_index,
459 };
460
461 match constructor.create_message(ctx) {
462 Ok(message) => {
463 if let Some(mut message) = message {
464 message.flags = self.sync_flag;
465 ui.send_message(message);
466 }
467 }
468 Err(e) => sync_errors.push(e),
469 }
470 }
471 }
472
473 if sync_errors.is_empty() {
474 Ok(())
475 } else {
476 Err(sync_errors)
477 }
478 }
479
480 pub fn property_editors(&self) -> impl Iterator<Item = &ContextEntry> + '_ {
481 self.entries.iter()
482 }
483
484 pub fn find_property_editor(&self, name: &str) -> Handle<UiNode> {
485 if let Some(property_editor) = self
486 .entries
487 .iter()
488 .find(|e| e.property_name == name)
489 .map(|e| e.property_editor)
490 {
491 return property_editor;
492 }
493
494 Default::default()
495 }
496}
497
498impl Control for Inspector {
499 fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
500 if type_id == TypeId::of::<Self>() {
501 Some(self)
502 } else {
503 None
504 }
505 }
506
507 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
508 self.widget.handle_routed_message(ui, message);
509
510 if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
511 {
512 if let Some(InspectorMessage::Context(ctx)) = message.data::<InspectorMessage>() {
513 for child in self.children() {
515 ui.send_message(WidgetMessage::remove(*child, MessageDirection::ToWidget));
516 }
517
518 ui.send_message(WidgetMessage::link(
520 ctx.stack_panel,
521 MessageDirection::ToWidget,
522 self.handle,
523 ));
524
525 self.context = ctx.clone();
526 }
527 }
528
529 if message.flags != self.context.sync_flag {
532 for entry in self.context.entries.iter() {
533 if message.destination() == entry.property_editor {
534 if let Some(args) = entry.property_editor_definition.translate_message(
535 &entry.property_name,
536 entry.property_owner_type_id,
537 message,
538 ) {
539 ui.send_message(InspectorMessage::property_changed(
540 self.handle,
541 MessageDirection::FromWidget,
542 args,
543 ));
544 }
545 }
546 }
547 }
548 }
549}
550
551pub struct InspectorBuilder {
552 widget_builder: WidgetBuilder,
553 context: InspectorContext,
554}
555
556impl InspectorBuilder {
557 pub fn new(widget_builder: WidgetBuilder) -> Self {
558 Self {
559 widget_builder,
560 context: Default::default(),
561 }
562 }
563
564 pub fn with_context(mut self, context: InspectorContext) -> Self {
565 self.context = context;
566 self
567 }
568
569 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
570 let canvas = Inspector {
571 widget: self
572 .widget_builder
573 .with_child(self.context.stack_panel)
574 .build(),
575 context: self.context,
576 };
577 ctx.add_node(UiNode::new(canvas))
578 }
579}