1mod widget_impl;
14
15use std::cell::RefCell;
16use std::rc::Rc;
17use std::sync::Arc;
18
19use crate::color::Color;
20use crate::draw_ctx::DrawCtx;
21use crate::event::{Event, EventResult};
22use crate::geometry::{Point, Rect, Size};
23use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
24use crate::text::Font;
25use crate::widget::{InspectorNode, InspectorOverlay, Widget};
26use crate::widgets::tree_view::TreeView;
27
28struct InternalPresenceNode {
44 bounds: Rect,
45 children: Vec<Box<dyn Widget>>,
46 base: WidgetBase,
47 name: &'static str,
48}
49
50impl Widget for InternalPresenceNode {
51 fn type_name(&self) -> &'static str {
52 self.name
53 }
54 fn bounds(&self) -> Rect {
55 self.bounds
56 }
57 fn set_bounds(&mut self, b: Rect) {
58 self.bounds = b;
59 }
60 fn children(&self) -> &[Box<dyn Widget>] {
61 &self.children
62 }
63 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> {
64 &mut self.children
65 }
66 fn margin(&self) -> Insets {
67 self.base.margin
68 }
69 fn widget_base(&self) -> Option<&WidgetBase> {
70 Some(&self.base)
71 }
72 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
73 Some(&mut self.base)
74 }
75 fn h_anchor(&self) -> HAnchor {
76 self.base.h_anchor
77 }
78 fn v_anchor(&self) -> VAnchor {
79 self.base.v_anchor
80 }
81 fn min_size(&self) -> Size {
82 self.base.min_size
83 }
84 fn max_size(&self) -> Size {
85 self.base.max_size
86 }
87 fn layout(&mut self, _: Size) -> Size {
88 Size::new(self.bounds.width, self.bounds.height)
89 }
90 fn paint(&mut self, _: &mut dyn DrawCtx) {}
91 fn hit_test(&self, _: Point) -> bool {
92 false
93 }
94 fn on_event(&mut self, _: &Event) -> EventResult {
95 EventResult::Ignored
96 }
97 fn contributes_children_to_inspector(&self) -> bool {
98 false
99 }
100}
101
102const DEFAULT_PROPS_H: f64 = 180.0;
104pub(super) const FONT_SIZE: f64 = 12.0;
105const HEADER_H: f64 = 30.0;
106const SPLIT_HIT: f64 = 5.0;
107const MIN_PROPS_H: f64 = 60.0;
108const MIN_TREE_H: f64 = 60.0;
109
110fn c_panel_bg(v: &crate::theme::Visuals) -> Color {
114 v.panel_fill
115}
116fn c_header_bg(v: &crate::theme::Visuals) -> Color {
117 let f = if is_dark(v) { 0.80 } else { 0.94 };
119 Color::rgba(
120 v.panel_fill.r * f,
121 v.panel_fill.g * f,
122 v.panel_fill.b * f,
123 1.0,
124 )
125}
126fn c_props_bg(v: &crate::theme::Visuals) -> Color {
127 v.window_fill
128}
129fn c_split_bg(v: &crate::theme::Visuals) -> Color {
130 let t = if is_dark(v) { 1.0 } else { 0.0 };
131 Color::rgba(t, t, t, 0.10)
132}
133pub(super) fn c_border(v: &crate::theme::Visuals) -> Color {
134 v.separator
135}
136pub(super) fn c_text(v: &crate::theme::Visuals) -> Color {
137 v.text_color
138}
139pub(super) fn c_dim_text(v: &crate::theme::Visuals) -> Color {
140 v.text_dim
141}
142
143fn is_dark(v: &crate::theme::Visuals) -> bool {
144 let lum = 0.299 * v.panel_fill.r + 0.587 * v.panel_fill.g + 0.114 * v.panel_fill.b;
146 lum < 0.5
147}
148
149fn translate_event(event: &Event, offset_y: f64) -> Event {
154 match event {
155 Event::MouseDown {
156 pos,
157 button,
158 modifiers,
159 } => Event::MouseDown {
160 pos: Point::new(pos.x, pos.y - offset_y),
161 button: *button,
162 modifiers: *modifiers,
163 },
164 Event::MouseMove { pos } => Event::MouseMove {
165 pos: Point::new(pos.x, pos.y - offset_y),
166 },
167 Event::MouseUp {
168 pos,
169 button,
170 modifiers,
171 } => Event::MouseUp {
172 pos: Point::new(pos.x, pos.y - offset_y),
173 button: *button,
174 modifiers: *modifiers,
175 },
176 Event::MouseWheel {
177 pos,
178 delta_y,
179 delta_x,
180 modifiers,
181 } => Event::MouseWheel {
182 pos: Point::new(pos.x, pos.y - offset_y),
183 delta_y: *delta_y,
184 delta_x: *delta_x,
185 modifiers: *modifiers,
186 },
187 other => other.clone(),
188 }
189}
190
191pub struct InspectorPanel {
194 bounds: Rect,
195 _children: Vec<Box<dyn Widget>>,
199 base: WidgetBase,
200 font: Arc<Font>,
201 nodes: Rc<RefCell<Vec<InspectorNode>>>,
202 selected: Option<usize>,
204 props_h: f64,
205 split_dragging: bool,
206 pub hovered_bounds: Rc<RefCell<Option<InspectorOverlay>>>,
208 pub(crate) tree_view: TreeView,
210 pending_expanded: Option<Vec<bool>>,
214 pending_selected: Option<Option<usize>>,
215 snapshot_out: Option<Rc<RefCell<Option<InspectorSavedState>>>>,
219 #[cfg(feature = "reflect")]
225 pub edits: Option<Rc<RefCell<Vec<crate::widget::InspectorEdit>>>>,
226 pub base_edits: Option<Rc<RefCell<Vec<crate::widget::WidgetBaseEdit>>>>,
229 prop_hits: Vec<PropHit>,
233 last_inspector_nodes_fingerprint: Option<(usize, usize)>,
240}
241
242#[derive(Clone, Debug)]
243#[cfg_attr(not(feature = "reflect"), allow(dead_code))]
244pub(super) struct PropHit {
245 pub(super) rect: Rect,
246 pub(super) field: String,
247 pub(super) kind: PropHitKind,
248}
249
250#[derive(Clone, Debug)]
251pub(super) enum PropHitKind {
252 #[cfg_attr(not(feature = "reflect"), allow(dead_code))]
254 BoolToggle { current: bool },
255 #[cfg_attr(not(feature = "reflect"), allow(dead_code))]
257 NumericStep { current: f64, step: f64 },
258 InsetField {
260 target: InsetsTarget,
261 side: InsetsSide,
262 current: f64,
263 step: f64,
264 },
265 HAnchorCycle { current_bits: u8 },
267 VAnchorCycle { current_bits: u8 },
269}
270
271#[derive(Clone, Copy, Debug)]
273pub(super) enum InsetsTarget {
274 Margin,
275 }
277
278#[derive(Clone, Copy, Debug)]
280pub(super) enum InsetsSide {
281 Left,
282 Right,
283 Top,
284 Bottom,
285}
286
287#[derive(Clone, Debug, Default)]
289#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
290pub struct InspectorSavedState {
291 pub expanded: Vec<bool>,
292 pub selected: Option<usize>,
293 pub props_h: f64,
294}
295
296impl InspectorPanel {
297 pub fn new(
298 font: Arc<Font>,
299 nodes: Rc<RefCell<Vec<InspectorNode>>>,
300 hovered_bounds: Rc<RefCell<Option<InspectorOverlay>>>,
301 ) -> Self {
302 let tree_view = TreeView::new(Arc::clone(&font))
310 .with_row_height(20.0)
311 .with_font_size(12.0)
312 .with_indent_width(14.0);
313 Self {
314 bounds: Rect::default(),
315 _children: vec![Box::new(InternalPresenceNode {
316 bounds: Rect::default(),
317 children: Vec::new(),
318 base: WidgetBase::new(),
319 name: "TreeView",
320 })],
321 base: WidgetBase::new(),
322 font,
323 nodes,
324 selected: None,
325 props_h: DEFAULT_PROPS_H,
326 split_dragging: false,
327 hovered_bounds,
328 tree_view,
329 #[cfg(feature = "reflect")]
330 edits: None,
331 base_edits: None,
332 prop_hits: Vec::new(),
333 pending_expanded: None,
334 pending_selected: None,
335 snapshot_out: None,
336 last_inspector_nodes_fingerprint: None,
337 }
338 }
339
340 pub fn with_snapshot_cell(mut self, cell: Rc<RefCell<Option<InspectorSavedState>>>) -> Self {
344 self.snapshot_out = Some(cell);
345 self
346 }
347
348 pub fn with_base_edit_queue(
353 mut self,
354 cell: Rc<RefCell<Vec<crate::widget::WidgetBaseEdit>>>,
355 ) -> Self {
356 self.base_edits = Some(cell);
357 self
358 }
359
360 #[cfg(feature = "reflect")]
366 pub fn with_edit_queue(mut self, cell: Rc<RefCell<Vec<crate::widget::InspectorEdit>>>) -> Self {
367 self.edits = Some(cell);
368 self
369 }
370
371 pub fn saved_state(&self) -> InspectorSavedState {
382 InspectorSavedState {
383 expanded: self.tree_view.nodes.iter().map(|n| n.is_expanded).collect(),
384 selected: self.tree_view.nodes.iter().position(|n| n.is_selected),
385 props_h: self.props_h,
386 }
387 }
388
389 pub fn apply_saved_state(&mut self, s: InspectorSavedState) {
394 self.pending_expanded = Some(s.expanded);
395 self.pending_selected = Some(s.selected);
396 self.props_h = s.props_h.clamp(MIN_PROPS_H, 1024.0);
397 }
398
399 fn list_area_h(&self) -> f64 {
403 (self.bounds.height - HEADER_H).max(0.0)
404 }
405
406 fn split_y(&self) -> f64 {
408 self.props_h.clamp(
409 MIN_PROPS_H,
410 (self.list_area_h() - MIN_TREE_H).max(MIN_PROPS_H),
411 )
412 }
413
414 fn tree_origin_y(&self) -> f64 {
416 self.split_y() + 4.0
417 }
418
419 fn on_split_handle(&self, pos: Point) -> bool {
420 let sy = self.split_y();
421 pos.y >= sy - SPLIT_HIT && pos.y <= sy + SPLIT_HIT
422 }
423
424 fn pos_in_tree_area(&self, pos: Point) -> bool {
425 let tree_bot = self.tree_origin_y();
426 let tree_top = self.list_area_h();
427 pos.y >= tree_bot && pos.y <= tree_top
428 }
429
430 fn forward_to_tree(&mut self, event: &Event) -> EventResult {
432 let offset_y = self.tree_view.bounds().y;
436 let translated = translate_event(event, offset_y);
437 self.tree_view.on_event(&translated)
438 }
439
440 fn update_hovered_bounds_from_tree(&self) {
441 let nodes = self.nodes.borrow();
442 let next = self
443 .tree_view
444 .hovered_node_idx()
445 .and_then(|i| nodes.get(i))
446 .map(|n| InspectorOverlay {
447 bounds: n.screen_bounds,
448 margin: n.margin,
449 padding: n.padding,
450 });
451 let mut hovered = self.hovered_bounds.borrow_mut();
452 if *hovered != next {
453 *hovered = next;
454 crate::animation::request_draw_without_invalidation();
455 }
456 }
457}
458
459fn next_h_anchor(bits: u8) -> HAnchor {
462 if bits == HAnchor::FIT.bits() {
464 HAnchor::STRETCH
465 } else if bits == HAnchor::STRETCH.bits() {
466 HAnchor::LEFT
467 } else if bits == HAnchor::LEFT.bits() {
468 HAnchor::CENTER
469 } else if bits == HAnchor::CENTER.bits() {
470 HAnchor::RIGHT
471 } else {
472 HAnchor::FIT
473 }
474}
475
476fn next_v_anchor(bits: u8) -> VAnchor {
477 if bits == VAnchor::FIT.bits() {
479 VAnchor::STRETCH
480 } else if bits == VAnchor::STRETCH.bits() {
481 VAnchor::BOTTOM
482 } else if bits == VAnchor::BOTTOM.bits() {
483 VAnchor::CENTER
484 } else if bits == VAnchor::CENTER.bits() {
485 VAnchor::TOP
486 } else {
487 VAnchor::FIT
488 }
489}
490
491impl InspectorPanel {
494 pub fn with_margin(mut self, m: Insets) -> Self {
495 self.base.margin = m;
496 self
497 }
498 pub fn with_h_anchor(mut self, h: HAnchor) -> Self {
499 self.base.h_anchor = h;
500 self
501 }
502 pub fn with_v_anchor(mut self, v: VAnchor) -> Self {
503 self.base.v_anchor = v;
504 self
505 }
506 pub fn with_min_size(mut self, s: Size) -> Self {
507 self.base.min_size = s;
508 self
509 }
510 pub fn with_max_size(mut self, s: Size) -> Self {
511 self.base.max_size = s;
512 self
513 }
514}
515
516impl InspectorPanel {
521 fn paint_properties(&mut self, ctx: &mut dyn DrawCtx, available_h: f64) {
522 let panel_y_offset = 0.0; self.prop_hits.clear();
524 super::inspector_props::paint_properties(
525 ctx,
526 available_h,
527 panel_y_offset,
528 self.bounds.width,
529 &self.font,
530 self.selected,
531 &self.nodes.borrow(),
532 &mut self.prop_hits,
533 );
534 }
535
536 fn try_emit_base_edit_from_click(&self, pos: Point) -> bool {
540 let Some(queue) = &self.base_edits else {
541 return false;
542 };
543 let Some(sel_idx) = self.selected else {
544 return false;
545 };
546 let nodes = self.nodes.borrow();
547 let Some(node) = nodes.get(sel_idx) else {
548 return false;
549 };
550 let Some(hit) = self.prop_hits.iter().find(|h| {
551 pos.x >= h.rect.x
552 && pos.x <= h.rect.x + h.rect.width
553 && pos.y >= h.rect.y
554 && pos.y <= h.rect.y + h.rect.height
555 }) else {
556 return false;
557 };
558 let field = match &hit.kind {
559 PropHitKind::InsetField {
560 target: InsetsTarget::Margin,
561 side,
562 current,
563 step,
564 } => {
565 let mid = hit.rect.x + hit.rect.width * 0.5;
566 let new_v = (if pos.x < mid {
567 *current - *step
568 } else {
569 *current + *step
570 })
571 .max(0.0);
572 match side {
573 InsetsSide::Left => crate::widget::WidgetBaseField::MarginLeft(new_v),
574 InsetsSide::Right => crate::widget::WidgetBaseField::MarginRight(new_v),
575 InsetsSide::Top => crate::widget::WidgetBaseField::MarginTop(new_v),
576 InsetsSide::Bottom => crate::widget::WidgetBaseField::MarginBottom(new_v),
577 }
578 }
579 PropHitKind::HAnchorCycle { current_bits } => {
580 crate::widget::WidgetBaseField::HAnchor(next_h_anchor(*current_bits))
581 }
582 PropHitKind::VAnchorCycle { current_bits } => {
583 crate::widget::WidgetBaseField::VAnchor(next_v_anchor(*current_bits))
584 }
585 _ => return false,
586 };
587 queue.borrow_mut().push(crate::widget::WidgetBaseEdit {
588 path: node.path.clone(),
589 field,
590 });
591 crate::animation::request_draw();
592 true
593 }
594
595 #[cfg(feature = "reflect")]
598 fn try_emit_edit_from_click(&self, pos: Point) -> bool {
599 let Some(queue) = &self.edits else {
600 return false;
601 };
602 let Some(sel_idx) = self.selected else {
603 return false;
604 };
605 let nodes = self.nodes.borrow();
606 let Some(node) = nodes.get(sel_idx) else {
607 return false;
608 };
609 let Some(hit) = self.prop_hits.iter().find(|h| {
610 pos.x >= h.rect.x
611 && pos.x <= h.rect.x + h.rect.width
612 && pos.y >= h.rect.y
613 && pos.y <= h.rect.y + h.rect.height
614 }) else {
615 return false;
616 };
617
618 let edit = match &hit.kind {
619 PropHitKind::BoolToggle { current } => crate::widget::InspectorEdit {
620 path: node.path.clone(),
621 field_path: hit.field.clone(),
622 new_value: Box::new(!*current),
623 },
624 PropHitKind::NumericStep { current, step } => {
625 let mid = hit.rect.x + hit.rect.width * 0.5;
626 let new_v = if pos.x < mid {
627 *current - *step
628 } else {
629 *current + *step
630 };
631 crate::widget::InspectorEdit {
632 path: node.path.clone(),
633 field_path: hit.field.clone(),
634 new_value: Box::new(new_v),
635 }
636 }
637 _ => return false,
638 };
639 queue.borrow_mut().push(edit);
640 crate::animation::request_draw();
641 true
642 }
643}