1use std::vec::Vec;
22
23use azul_core::{
24 callbacks::{CoreCallback, CoreCallbackData, Update},
25 dom::{
26 Dom, DomVec, EventFilter, HoverEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec,
27 TabIndex,
28 },
29 refany::{OptionRefAny, RefAny},
30};
31use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
32use azul_css::{
33 impl_option, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_mut, impl_vec_partialeq,
34 props::{
35 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, StyleFontSize},
36 layout::{LayoutDisplay, LayoutFlexDirection, LayoutFlexGrow, LayoutOverflow, LayoutAlignItems, LayoutPaddingTop, LayoutPaddingBottom, LayoutPaddingLeft, LayoutPaddingRight},
37 property::{CssProperty, *},
38 style::{StyleBackgroundContent, StyleBackgroundContentVec, StyleTextColor, LayoutBorderTopWidth, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth, StyleBorderTopStyle, BorderStyle, StyleBorderBottomStyle, StyleBorderLeftStyle, StyleBorderRightStyle, StyleBorderTopColor, StyleBorderBottomColor, StyleBorderLeftColor, StyleBorderRightColor, StyleBorderTopLeftRadius, StyleBorderTopRightRadius, StyleBorderBottomLeftRadius, StyleBorderBottomRightRadius, StyleCursor, StyleUserSelect, StyleTextAlign},
39 },
40 impl_option_inner, AzString,
41};
42
43use crate::callbacks::{Callback, CallbackInfo};
44
45static ACCORDION_CLASS: &[IdOrClass] =
46 &[Class(AzString::from_const_str("__azul-native-accordion"))];
47static ACCORDION_SECTION_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
48 "__azul-native-accordion-section",
49))];
50static ACCORDION_HEADER_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
51 "__azul-native-accordion-header",
52))];
53static ACCORDION_TITLE_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str(
54 "__azul-native-accordion-title",
55))];
56static ACCORDION_BODY_CLASS: &[IdOrClass] =
57 &[Class(AzString::from_const_str("__azul-native-accordion-body"))];
58
59const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
60const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
61const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
62 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
63
64pub type AccordionOnToggleCallbackType = extern "C" fn(RefAny, CallbackInfo, usize) -> Update;
67impl_widget_callback!(
68 AccordionOnToggle,
69 OptionAccordionOnToggle,
70 AccordionOnToggleCallback,
71 AccordionOnToggleCallbackType
72);
73
74azul_core::impl_managed_callback! {
75 wrapper: AccordionOnToggleCallback,
76 info_ty: CallbackInfo,
77 return_ty: Update,
78 default_ret: Update::DoNothing,
79 invoker_static: ACCORDION_ON_TOGGLE_INVOKER,
80 invoker_ty: AzAccordionOnToggleCallbackInvoker,
81 thunk_fn: az_accordion_on_toggle_callback_thunk,
82 setter_fn: AzApp_setAccordionOnToggleCallbackInvoker,
83 from_handle_fn: AzAccordionOnToggleCallback_createFromHostHandle,
84 extra_args: [ section_index: usize ],
85}
86
87const BORDER_COLOR: ColorU = ColorU { r: 222, g: 226, b: 230, a: 255 }; const HEADER_BG: ColorU = ColorU { r: 248, g: 249, b: 250, a: 255 }; const TEXT_COLOR: ColorU = ColorU { r: 33, g: 37, b: 41, a: 255 }; const HEADER_BG_ITEMS: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(HEADER_BG)];
93const HEADER_BG_VEC: StyleBackgroundContentVec =
94 StyleBackgroundContentVec::from_const_slice(HEADER_BG_ITEMS);
95
96#[derive(Debug, Clone, PartialEq, Eq)]
98#[repr(C)]
99pub struct AccordionSection {
100 pub title: AzString,
102 pub content: Dom,
104 pub is_open: bool,
106}
107
108impl AccordionSection {
109 pub fn new<S: Into<AzString>>(title: S, content: Dom) -> Self {
111 Self {
112 title: title.into(),
113 content,
114 is_open: false,
115 }
116 }
117
118 #[must_use] pub const fn with_open(mut self, open: bool) -> Self {
120 self.is_open = open;
121 self
122 }
123}
124
125impl_option!(AccordionSection, OptionAccordionSection, copy = false, [Debug, Clone, PartialEq, Eq]);
126impl_vec!(
127 AccordionSection,
128 AccordionSectionVec,
129 AccordionSectionVecDestructor,
130 AccordionSectionVecDestructorType,
131 AccordionSectionVecSlice,
132 OptionAccordionSection
133);
134impl_vec_clone!(AccordionSection, AccordionSectionVec, AccordionSectionVecDestructor);
135impl_vec_debug!(AccordionSection, AccordionSectionVec);
136impl_vec_partialeq!(AccordionSection, AccordionSectionVec);
137impl_vec_mut!(AccordionSection, AccordionSectionVec);
138
139#[derive(Debug, Clone, PartialEq)]
141#[repr(C)]
142pub struct Accordion {
143 pub sections: AccordionSectionVec,
145 pub on_toggle: OptionAccordionOnToggle,
147}
148
149static ACCORDION_CONTAINER_STYLE: &[CssPropertyWithConditions] = &[
152 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
153 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
154 LayoutFlexDirection::Column,
155 )),
156 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
157 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
158 CssPropertyWithConditions::simple(CssProperty::const_font_family(SYSTEM_UI_FAMILY)),
159 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
160 inner: TEXT_COLOR,
161 })),
162 CssPropertyWithConditions::simple(CssProperty::const_border_top_width(
164 LayoutBorderTopWidth::const_px(1),
165 )),
166 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
167 LayoutBorderBottomWidth::const_px(1),
168 )),
169 CssPropertyWithConditions::simple(CssProperty::const_border_left_width(
170 LayoutBorderLeftWidth::const_px(1),
171 )),
172 CssPropertyWithConditions::simple(CssProperty::const_border_right_width(
173 LayoutBorderRightWidth::const_px(1),
174 )),
175 CssPropertyWithConditions::simple(CssProperty::const_border_top_style(StyleBorderTopStyle {
176 inner: BorderStyle::Solid,
177 })),
178 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
179 StyleBorderBottomStyle {
180 inner: BorderStyle::Solid,
181 },
182 )),
183 CssPropertyWithConditions::simple(CssProperty::const_border_left_style(StyleBorderLeftStyle {
184 inner: BorderStyle::Solid,
185 })),
186 CssPropertyWithConditions::simple(CssProperty::const_border_right_style(
187 StyleBorderRightStyle {
188 inner: BorderStyle::Solid,
189 },
190 )),
191 CssPropertyWithConditions::simple(CssProperty::const_border_top_color(StyleBorderTopColor {
192 inner: BORDER_COLOR,
193 })),
194 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
195 StyleBorderBottomColor {
196 inner: BORDER_COLOR,
197 },
198 )),
199 CssPropertyWithConditions::simple(CssProperty::const_border_left_color(StyleBorderLeftColor {
200 inner: BORDER_COLOR,
201 })),
202 CssPropertyWithConditions::simple(CssProperty::const_border_right_color(
203 StyleBorderRightColor {
204 inner: BORDER_COLOR,
205 },
206 )),
207 CssPropertyWithConditions::simple(CssProperty::const_border_top_left_radius(
209 StyleBorderTopLeftRadius::const_px(6),
210 )),
211 CssPropertyWithConditions::simple(CssProperty::const_border_top_right_radius(
212 StyleBorderTopRightRadius::const_px(6),
213 )),
214 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_left_radius(
215 StyleBorderBottomLeftRadius::const_px(6),
216 )),
217 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_right_radius(
218 StyleBorderBottomRightRadius::const_px(6),
219 )),
220 CssPropertyWithConditions::simple(CssProperty::const_overflow_x(LayoutOverflow::Hidden)),
221 CssPropertyWithConditions::simple(CssProperty::const_overflow_y(LayoutOverflow::Hidden)),
222];
223
224static ACCORDION_SECTION_STYLE: &[CssPropertyWithConditions] = &[
225 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
226 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(
227 LayoutFlexDirection::Column,
228 )),
229 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
230 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_width(
232 LayoutBorderBottomWidth::const_px(1),
233 )),
234 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_style(
235 StyleBorderBottomStyle {
236 inner: BorderStyle::Solid,
237 },
238 )),
239 CssPropertyWithConditions::simple(CssProperty::const_border_bottom_color(
240 StyleBorderBottomColor {
241 inner: BORDER_COLOR,
242 },
243 )),
244];
245
246static ACCORDION_HEADER_STYLE: &[CssPropertyWithConditions] = &[
247 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
248 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)),
249 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
250 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
251 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
252 10,
253 ))),
254 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
255 LayoutPaddingBottom::const_px(10),
256 )),
257 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(
258 12,
259 ))),
260 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
261 LayoutPaddingRight::const_px(12),
262 )),
263 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
264 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
265 CssPropertyWithConditions::simple(CssProperty::const_background_content(HEADER_BG_VEC)),
266];
267
268static ACCORDION_TITLE_STYLE: &[CssPropertyWithConditions] = &[
269 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(1))),
270 CssPropertyWithConditions::simple(CssProperty::const_text_align(StyleTextAlign::Left)),
271];
272
273static ACCORDION_BODY_STYLE_OPEN: &[CssPropertyWithConditions] = &[
275 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Block)),
276 CssPropertyWithConditions::simple(CssProperty::const_padding_top(LayoutPaddingTop::const_px(
277 12,
278 ))),
279 CssPropertyWithConditions::simple(CssProperty::const_padding_bottom(
280 LayoutPaddingBottom::const_px(12),
281 )),
282 CssPropertyWithConditions::simple(CssProperty::const_padding_left(LayoutPaddingLeft::const_px(
283 12,
284 ))),
285 CssPropertyWithConditions::simple(CssProperty::const_padding_right(
286 LayoutPaddingRight::const_px(12),
287 )),
288];
289
290static ACCORDION_BODY_STYLE_CLOSED: &[CssPropertyWithConditions] = &[
292 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::None)),
293];
294
295impl Accordion {
296 #[must_use] pub fn new(sections: AccordionSectionVec) -> Self {
298 Self {
299 sections,
300 on_toggle: None.into(),
301 }
302 }
303
304 #[must_use] pub fn create() -> Self {
306 Self::new(AccordionSectionVec::from_const_slice(&[]))
307 }
308
309 pub fn set_on_toggle<C: Into<AccordionOnToggleCallback>>(&mut self, data: RefAny, callback: C) {
311 self.on_toggle = Some(AccordionOnToggle {
312 callback: callback.into(),
313 refany: data,
314 })
315 .into();
316 }
317
318 #[must_use] pub fn with_on_toggle<C: Into<AccordionOnToggleCallback>>(
320 mut self,
321 data: RefAny,
322 callback: C,
323 ) -> Self {
324 self.set_on_toggle(data, callback);
325 self
326 }
327
328 #[must_use] pub fn swap_with_default(&mut self) -> Self {
330 let mut s = Self::create();
331 core::mem::swap(&mut s, self);
332 s
333 }
334
335 #[must_use] pub fn dom(self) -> Dom {
337 let on_toggle = self.on_toggle;
338 let sections = self.sections;
339
340 let mut section_doms: Vec<Dom> = Vec::with_capacity(sections.as_ref().len());
341
342 for (index, section) in sections.as_ref().iter().enumerate() {
343 let title = Dom::create_text(section.title.clone())
344 .with_ids_and_classes(IdOrClassVec::from_const_slice(ACCORDION_TITLE_CLASS))
345 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
346 ACCORDION_TITLE_STYLE,
347 ));
348
349 let header_data = HeaderClickData {
351 index,
352 is_open: section.is_open,
353 on_toggle: clone_option_on_toggle(&on_toggle),
354 };
355
356 let header = Dom::create_div()
357 .with_ids_and_classes(IdOrClassVec::from_const_slice(ACCORDION_HEADER_CLASS))
358 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
359 ACCORDION_HEADER_STYLE,
360 ))
361 .with_tab_index(TabIndex::Auto)
362 .with_callbacks(
363 alloc::vec![CoreCallbackData {
364 event: EventFilter::Hover(HoverEventFilter::MouseUp),
365 callback: CoreCallback {
366 cb: on_accordion_header_click as usize,
367 ctx: OptionRefAny::None,
368 },
369 refany: RefAny::new(header_data),
370 }]
371 .into(),
372 )
373 .with_children(DomVec::from_vec(alloc::vec![title]));
374
375 let body_style = if section.is_open {
376 ACCORDION_BODY_STYLE_OPEN
377 } else {
378 ACCORDION_BODY_STYLE_CLOSED
379 };
380 let body = Dom::create_div()
381 .with_ids_and_classes(IdOrClassVec::from_const_slice(ACCORDION_BODY_CLASS))
382 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(body_style))
383 .with_children(DomVec::from_vec(alloc::vec![section.content.clone()]));
384
385 section_doms.push(
386 Dom::create_div()
387 .with_ids_and_classes(IdOrClassVec::from_const_slice(ACCORDION_SECTION_CLASS))
388 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
389 ACCORDION_SECTION_STYLE,
390 ))
391 .with_children(DomVec::from_vec(alloc::vec![header, body])),
392 );
393 }
394
395 Dom::create_div()
396 .with_ids_and_classes(IdOrClassVec::from_const_slice(ACCORDION_CLASS))
397 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
398 ACCORDION_CONTAINER_STYLE,
399 ))
400 .with_children(DomVec::from_vec(section_doms))
401 }
402}
403
404impl Default for Accordion {
405 fn default() -> Self {
406 Self::create()
407 }
408}
409
410fn clone_option_on_toggle(opt: &OptionAccordionOnToggle) -> OptionAccordionOnToggle {
412 match opt.as_ref() {
413 Some(AccordionOnToggle { callback, refany }) => Some(AccordionOnToggle {
414 callback: callback.clone(),
415 refany: refany.clone(),
416 })
417 .into(),
418 None => None.into(),
419 }
420}
421
422struct HeaderClickData {
424 index: usize,
425 is_open: bool,
426 on_toggle: OptionAccordionOnToggle,
427}
428
429extern "C" fn on_accordion_header_click(mut data: RefAny, mut info: CallbackInfo) -> Update {
434 let header = info.get_hit_node();
435 let Some(body) = info.get_next_sibling(header) else {
436 return Update::DoNothing;
437 };
438
439 let (now_open, result) = {
440 let Some(mut hd) = data.downcast_mut::<HeaderClickData>() else {
441 return Update::DoNothing;
442 };
443 hd.is_open = !hd.is_open;
444 let now_open = hd.is_open;
445 let index = hd.index;
446 let result = match hd.on_toggle.as_mut() {
447 Some(AccordionOnToggle { callback, refany }) => {
448 (callback.cb)(refany.clone(), info, index)
449 }
450 None => Update::DoNothing,
451 };
452 (now_open, result)
453 };
454
455 let display = if now_open {
456 LayoutDisplay::Block
457 } else {
458 LayoutDisplay::None
459 };
460 info.set_css_property(body, CssProperty::const_display(display));
461
462 result
463}
464
465impl From<Accordion> for Dom {
466 fn from(a: Accordion) -> Self {
467 a.dom()
468 }
469}