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 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
use crate::prelude::*;
/// A dropdown is used to display some state with the ability to open a popup with options to change that state.
///
/// Usually a dropdown is used in the context of a "combobox" or "picklist" to allow the user to select
/// from one of several discrete options. The dropdown takes two closures, one which shows the current state
/// regardless of whether the dropdown is open or closed, and one which shows the contents while it is open.
///
/// ## Basic Dropdown
///
/// A basic dropdown displaying five options that the user can choose from.
///
/// ```
/// # use vizia_core::prelude::*;
/// #
/// # #[derive(Lens)]
/// # struct AppData {
/// # value: u8,
/// # }
/// #
/// # impl Model for AppData {}
/// #
/// # enum AppEvent {
/// # SetValue(u8),
/// # }
/// #
/// # let cx = &mut Context::default();
/// #
/// # AppData { value: 0 }.build(cx);
/// #
/// Dropdown::new(
/// cx,
/// |cx| Label::new(cx, AppData::value),
/// |cx| {
/// for i in 0..5 {
/// Label::new(cx, i)
/// .on_press(move |cx| {
/// cx.emit(AppEvent::SetValue(i));
/// cx.emit(PopupEvent::Close); // close the popup
/// })
/// .width(Stretch(1.0));
/// }
/// },
/// )
/// .width(Pixels(100.0));
/// ```
///
/// The line marked "close the popup" is not required for anything other than closing the popup -
/// if you leave it out, the popup will simply not close until the user clicks out of the dropdown.
///
/// ## Custom Dropdown
///
/// The dropdown doesn't have to be the current state and then a set of options - it can contain any
/// set of views in either location. Here's an example where you can use a textbox to filter a list
/// of checkboxes which pop up when you click the textbox:
///
/// ```
/// # use vizia_core::prelude::*;
/// # let cx = &mut Context::default();
///
/// #[derive(Lens, Clone, PartialEq, Eq)]
/// struct AppData {
/// values: [bool; 6],
/// filter: String,
/// }
///
/// # impl Data for AppData {
/// # fn same(&self, other: &Self) -> bool {
/// # self == other
/// # }
/// # }
/// #
/// # #[derive(Debug)]
/// # enum AppEvent {
/// # SetFilter(String),
/// # SetValue(usize, bool),
/// # }
/// #
/// # impl Model for AppData {
/// # fn event(&mut self, _cx: &mut EventContext, event: &mut Event) {
/// # event.map(|msg, _| {
/// # match msg {
/// # AppEvent::SetFilter(s) => self.filter = s.clone(),
/// # AppEvent::SetValue(i, b) => self.values[*i] = *b,
/// # }
/// # });
/// # }
/// # }
/// #
/// # const LABELS: [&str; 6] = ["Bees", "Butterflies", "Dragonflies", "Crickets", "Moths", "Ladybugs"];
/// #
/// # AppData {
/// # values: [true, false, true, false, true, false],
/// # filter: "".to_owned(),
/// # }.build(cx);
///
/// Dropdown::new(cx, |cx| {
/// Textbox::new(cx, AppData::filter).on_edit(|cx, text| {
/// cx.emit(AppEvent::SetFilter(text));
/// })
/// .width(Pixels(100.0))
/// .height(Pixels(30.0))
/// }, |cx| {
/// Binding::new(cx, AppData::root, |cx, lens| {
/// let current = lens.get(cx);
/// for i in 0..6 {
/// if LABELS[i].to_lowercase().contains(¤t.filter.to_lowercase()) {
/// HStack::new(cx, move |cx| {
/// Checkbox::new(cx, AppData::values.map(move |x| x[i]))
/// .on_toggle(move |cx| {
/// cx.emit(AppEvent::SetValue(i, !current.values[i]));
/// });
/// Label::new(cx, LABELS[i]);
/// });
/// }
/// }
/// });
/// }).width(Pixels(100.0));
/// ```
pub struct Dropdown;
impl Dropdown {
/// Creates a new dropdown.
///
/// # Example
///
/// ```
/// # use vizia_core::prelude::*;
/// #
/// # let cx = &mut Context::default();
/// #
/// Dropdown::new(cx, |cx| Label::new(cx, "Text"), |_| {});
/// ```
pub fn new<F, L>(cx: &mut Context, trigger: L, content: F) -> Handle<Self>
where
L: 'static + Fn(&mut Context),
F: 'static + Fn(&mut Context),
{
Self {}.build(cx, move |cx| {
cx.add_listener(move |_dropdown: &mut Self, cx, event| {
event.map(|window_event, meta| match window_event {
WindowEvent::PressDown { mouse: _ } => {
if meta.origin != cx.current() {
// Check if the mouse was pressed outside of any descendants
if !cx.hovered.is_descendant_of(cx.tree, cx.current) {
cx.emit(PopupEvent::Close);
}
}
}
WindowEvent::KeyDown(code, _) => {
if *code == Code::Escape {
cx.emit(PopupEvent::Close);
}
}
_ => {}
});
});
PopupData::default().build(cx);
(trigger)(cx);
// .class("dropdown-title")
// .width(Stretch(1.0))
// .checked(PopupData::is_open)
// .navigable(true)
// .on_press(|cx| cx.emit(PopupEvent::Switch));
Binding::new(cx, PopupData::is_open, move |cx, is_open| {
if is_open.get(cx) {
Popup::new(cx, |cx| {
(content)(cx);
})
.arrow_size(Pixels(4.0));
}
})
})
}
}
impl View for Dropdown {
fn element(&self) -> Option<&'static str> {
Some("dropdown")
}
}