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
use crate::{icons::ICON_CHEVRON_DOWN, prelude::*};
/// Events that can be triggered by the collapsible view.
pub enum CollapsibleEvent {
ToggleOpen,
}
/// A collapsible view that can be opened or closed to hide content.
///
/// # Example
/// ```no_run
/// # use vizia_core::prelude::*;
/// # let cx = &mut Context::default();
/// Collapsible::new(
/// cx,
/// |cx| {
/// Label::new(cx, "Click me to collapse the content").hoverable(false);
/// },
/// |cx| {
/// Label::new(cx, "Line 1\nLine 2\nLine 3\nLine 4\nLine 5").hoverable(false);
/// },
/// )
/// .width(Pixels(300.0));
/// ```
pub struct Collapsible {
is_open: Signal<bool>,
on_toggle: Option<Box<dyn Fn(&mut EventContext, bool)>>,
}
impl Collapsible {
/// Create a new collapsible view with a header and content.
pub fn new(
cx: &mut Context,
header: impl Fn(&mut Context),
content: impl Fn(&mut Context),
) -> Handle<Self> {
let is_open = Signal::new(false);
Self { is_open, on_toggle: None }
.build(cx, |cx| {
let entity = cx.current();
// Header
HStack::new(cx, |cx| {
header(cx);
Svg::new(cx, ICON_CHEVRON_DOWN)
.class("expand-icon")
.on_press(|cx| cx.emit(CollapsibleEvent::ToggleOpen));
})
.navigable(true)
.role(Role::Button)
.expanded(is_open)
.class("header")
.controls(format!("{}", entity))
.on_press(|cx| cx.emit(CollapsibleEvent::ToggleOpen));
// Content
VStack::new(cx, |cx| {
content(cx);
})
.id(format!("{}", entity))
.class("content");
})
.toggle_class("open", is_open)
}
}
impl View for Collapsible {
fn element(&self) -> Option<&'static str> {
Some("collapsible")
}
fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
event.map(|collapsible_event, _| match collapsible_event {
CollapsibleEvent::ToggleOpen => {
self.is_open.set(!self.is_open.get());
if let Some(callback) = &self.on_toggle {
(callback)(cx, self.is_open.get());
}
}
});
}
}
impl Handle<'_, Collapsible> {
/// Set the open state of the collapsible view.
pub fn open(self, open: impl Res<bool> + 'static) -> Self {
let open = open.to_signal(self.cx);
self.bind(open, move |handle| {
let open = open.get();
handle.modify(|collapsible| {
collapsible.is_open.set_if_changed(open);
});
})
}
/// Set the callback which is triggered when the open state changes.
pub fn on_toggle(self, callback: impl Fn(&mut EventContext, bool) + 'static) -> Self {
self.modify(|collapsible| collapsible.on_toggle = Some(Box::new(callback)))
}
}