rg3d_ui/
expander.rs

1use crate::{
2    check_box::{CheckBoxBuilder, CheckBoxMessage},
3    core::pool::Handle,
4    define_constructor,
5    grid::{Column, GridBuilder, Row},
6    message::{MessageDirection, UiMessage},
7    utils::{make_arrow, ArrowDirection},
8    widget::{Widget, WidgetBuilder, WidgetMessage},
9    BuildContext, Control, UiNode, UserInterface, VerticalAlignment,
10};
11use std::{
12    any::{Any, TypeId},
13    ops::{Deref, DerefMut},
14};
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum ExpanderMessage {
18    Expand(bool),
19}
20
21impl ExpanderMessage {
22    define_constructor!(ExpanderMessage:Expand => fn expand(bool), layout: false);
23}
24
25#[derive(Clone)]
26pub struct Expander {
27    widget: Widget,
28    content: Handle<UiNode>,
29    expander: Handle<UiNode>,
30    is_expanded: bool,
31}
32
33crate::define_widget_deref!(Expander);
34
35impl Control for Expander {
36    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
37        if type_id == TypeId::of::<Self>() {
38            Some(self)
39        } else {
40            None
41        }
42    }
43
44    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
45        if let Some(&ExpanderMessage::Expand(expand)) = message.data::<ExpanderMessage>() {
46            if message.destination() == self.handle()
47                && message.direction() == MessageDirection::ToWidget
48                && self.is_expanded != expand
49            {
50                // Switch state of expander.
51                ui.send_message(CheckBoxMessage::checked(
52                    self.expander,
53                    MessageDirection::ToWidget,
54                    Some(expand),
55                ));
56                // Show or hide content.
57                ui.send_message(WidgetMessage::visibility(
58                    self.content,
59                    MessageDirection::ToWidget,
60                    expand,
61                ));
62                self.is_expanded = expand;
63            }
64        } else if let Some(CheckBoxMessage::Check(value)) = message.data::<CheckBoxMessage>() {
65            if message.destination() == self.expander
66                && message.direction() == MessageDirection::FromWidget
67            {
68                ui.send_message(ExpanderMessage::expand(
69                    self.handle,
70                    MessageDirection::ToWidget,
71                    value.unwrap_or(false),
72                ));
73            }
74        }
75        self.widget.handle_routed_message(ui, message);
76    }
77}
78
79pub struct ExpanderBuilder {
80    pub widget_builder: WidgetBuilder,
81    header: Handle<UiNode>,
82    content: Handle<UiNode>,
83    check_box: Handle<UiNode>,
84    is_expanded: bool,
85    expander_column: Option<Column>,
86}
87
88impl ExpanderBuilder {
89    pub fn new(widget_builder: WidgetBuilder) -> Self {
90        Self {
91            widget_builder,
92            header: Handle::NONE,
93            content: Handle::NONE,
94            check_box: Default::default(),
95            is_expanded: true,
96            expander_column: None,
97        }
98    }
99
100    pub fn with_header(mut self, header: Handle<UiNode>) -> Self {
101        self.header = header;
102        self
103    }
104
105    pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
106        self.content = content;
107        self
108    }
109
110    pub fn with_expanded(mut self, expanded: bool) -> Self {
111        self.is_expanded = expanded;
112        self
113    }
114
115    pub fn with_checkbox(mut self, check_box: Handle<UiNode>) -> Self {
116        self.check_box = check_box;
117        self
118    }
119
120    pub fn with_expander_column(mut self, expander_column: Column) -> Self {
121        self.expander_column = Some(expander_column);
122        self
123    }
124
125    pub fn build(self, ctx: &mut BuildContext<'_>) -> Handle<UiNode> {
126        let expander = if self.check_box.is_some() {
127            self.check_box
128        } else {
129            CheckBoxBuilder::new(
130                WidgetBuilder::new().with_vertical_alignment(VerticalAlignment::Center),
131            )
132            .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
133            .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
134            .checked(Some(self.is_expanded))
135            .build(ctx)
136        };
137
138        ctx[expander].set_row(0).set_column(0);
139
140        if self.header.is_some() {
141            ctx[self.header].set_row(0).set_column(1);
142        }
143
144        let grid = GridBuilder::new(
145            WidgetBuilder::new()
146                .with_child(expander)
147                .with_child(self.header),
148        )
149        .add_row(Row::auto())
150        .add_column(self.expander_column.unwrap_or_else(Column::auto))
151        .add_column(Column::stretch())
152        .build(ctx);
153
154        if self.content.is_some() {
155            ctx[self.content]
156                .set_row(1)
157                .set_column(0)
158                .set_visibility(self.is_expanded);
159        }
160
161        let e = UiNode::new(Expander {
162            widget: self
163                .widget_builder
164                .with_child(
165                    GridBuilder::new(
166                        WidgetBuilder::new()
167                            .with_child(grid)
168                            .with_child(self.content),
169                    )
170                    .add_column(Column::auto())
171                    .add_row(Row::auto())
172                    .add_row(Row::stretch())
173                    .build(ctx),
174                )
175                .build(),
176            content: self.content,
177            expander,
178            is_expanded: self.is_expanded,
179        });
180        ctx.add_node(e)
181    }
182}