1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::{
    check_box::CheckBoxBuilder,
    core::pool::Handle,
    grid::{Column, GridBuilder, Row},
    message::{
        CheckBoxMessage, ExpanderMessage, MessageData, MessageDirection, UiMessage, UiMessageData,
        WidgetMessage,
    },
    utils::{make_arrow, ArrowDirection},
    widget::{Widget, WidgetBuilder},
    BuildContext, Control, UINode, UserInterface, VerticalAlignment,
};
use std::ops::{Deref, DerefMut};

#[derive(Clone)]
pub struct Expander<M: MessageData, C: Control<M, C>> {
    widget: Widget<M, C>,
    header: Handle<UINode<M, C>>,
    content: Handle<UINode<M, C>>,
    expander: Handle<UINode<M, C>>,
    is_expanded: bool,
}

crate::define_widget_deref!(Expander<M, C>);

impl<M: MessageData, C: Control<M, C>> Control<M, C> for Expander<M, C> {
    fn handle_routed_message(
        &mut self,
        ui: &mut UserInterface<M, C>,
        message: &mut UiMessage<M, C>,
    ) {
        match *message.data() {
            UiMessageData::Expander(ExpanderMessage::Expand(expand)) => {
                if message.destination() == self.handle()
                    && message.direction() == MessageDirection::ToWidget
                    && self.is_expanded != expand
                {
                    // Switch state of expander.
                    ui.send_message(CheckBoxMessage::checked(
                        self.expander,
                        MessageDirection::ToWidget,
                        Some(expand),
                    ));
                    // Show or hide content.
                    ui.send_message(WidgetMessage::visibility(
                        self.content,
                        MessageDirection::ToWidget,
                        expand,
                    ));
                    self.is_expanded = expand;
                }
            }
            UiMessageData::CheckBox(CheckBoxMessage::Check(value)) => {
                if message.destination() == self.expander
                    && message.direction() == MessageDirection::FromWidget
                {
                    ui.send_message(ExpanderMessage::expand(
                        self.handle,
                        MessageDirection::ToWidget,
                        value.unwrap_or(false),
                    ));
                }
            }
            _ => {}
        }

        self.widget.handle_routed_message(ui, message);
    }
}

pub struct ExpanderBuilder<M: MessageData, C: Control<M, C>> {
    pub widget_builder: WidgetBuilder<M, C>,
    header: Handle<UINode<M, C>>,
    content: Handle<UINode<M, C>>,
    is_expanded: bool,
}

impl<M: MessageData, C: Control<M, C>> ExpanderBuilder<M, C> {
    pub fn new(widget_builder: WidgetBuilder<M, C>) -> Self {
        Self {
            widget_builder,
            header: Handle::NONE,
            content: Handle::NONE,
            is_expanded: true,
        }
    }

    pub fn with_header(mut self, header: Handle<UINode<M, C>>) -> Self {
        self.header = header;
        self
    }

    pub fn with_content(mut self, content: Handle<UINode<M, C>>) -> Self {
        self.content = content;
        self
    }

    pub fn with_expanded(mut self, expanded: bool) -> Self {
        self.is_expanded = expanded;
        self
    }

    pub fn build(self, ctx: &mut BuildContext<'_, M, C>) -> Handle<UINode<M, C>> {
        let expander = CheckBoxBuilder::new(
            WidgetBuilder::new().with_vertical_alignment(VerticalAlignment::Center),
        )
        .with_check_mark(make_arrow(ctx, ArrowDirection::Bottom, 8.0))
        .with_uncheck_mark(make_arrow(ctx, ArrowDirection::Right, 8.0))
        .with_content(self.header)
        .checked(Some(self.is_expanded))
        .build(ctx);

        if self.content.is_some() {
            ctx[self.content]
                .set_row(1)
                .set_column(0)
                .set_visibility(self.is_expanded);
        }

        let e = UINode::Expander(Expander {
            widget: self
                .widget_builder
                .with_child(
                    GridBuilder::new(
                        WidgetBuilder::new()
                            .with_child(expander)
                            .with_child(self.content),
                    )
                    .add_column(Column::auto())
                    .add_row(Row::strict(24.0))
                    .add_row(Row::stretch())
                    .build(ctx),
                )
                .build(),
            header: self.header,
            content: self.content,
            expander,
            is_expanded: self.is_expanded,
        });
        ctx.add_node(e)
    }
}