1use std::vec::Vec;
22
23use azul_core::{
24 callbacks::{CoreCallbackData, Update},
25 dom::{Dom, IdOrClass, IdOrClass::Class, IdOrClassVec, TabIndex},
26 refany::RefAny,
27};
28use azul_css::dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec};
29use azul_css::{
30 props::{
31 basic::{color::ColorU, StyleFontSize, StyleFontWeight},
32 layout::{LayoutDisplay, LayoutFlexDirection, LayoutAlignItems, LayoutAlignSelf, LayoutFlexGrow, LayoutMarginLeft, LayoutMarginRight},
33 property::{CssProperty, *},
34 style::{StyleCursor, StyleUserSelect, StyleTextColor},
35 },
36 impl_option_inner, AzString, StringVec,
37};
38
39use crate::callbacks::{Callback, CallbackInfo};
40
41static BREADCRUMB_CLASS: &[IdOrClass] =
42 &[Class(AzString::from_const_str("__azul-native-breadcrumb"))];
43static BREADCRUMB_ITEM_CLASS: &[IdOrClass] =
44 &[Class(AzString::from_const_str("__azul-native-breadcrumb-item"))];
45static BREADCRUMB_CURRENT_CLASS: &[IdOrClass] =
46 &[Class(AzString::from_const_str("__azul-native-breadcrumb-current"))];
47static BREADCRUMB_SEPARATOR_CLASS: &[IdOrClass] =
48 &[Class(AzString::from_const_str("__azul-native-breadcrumb-separator"))];
49
50const SEPARATOR_GLYPH: AzString = AzString::from_const_str("/");
52
53pub type BreadcrumbOnNavigateCallbackType =
55 extern "C" fn(RefAny, CallbackInfo, BreadcrumbState) -> Update;
56impl_widget_callback!(
57 BreadcrumbOnNavigate,
58 OptionBreadcrumbOnNavigate,
59 BreadcrumbOnNavigateCallback,
60 BreadcrumbOnNavigateCallbackType
61);
62
63azul_core::impl_managed_callback! {
64 wrapper: BreadcrumbOnNavigateCallback,
65 info_ty: CallbackInfo,
66 return_ty: Update,
67 default_ret: Update::DoNothing,
68 invoker_static: BREADCRUMB_ON_NAVIGATE_INVOKER,
69 invoker_ty: AzBreadcrumbOnNavigateCallbackInvoker,
70 thunk_fn: az_breadcrumb_on_navigate_callback_thunk,
71 setter_fn: AzApp_setBreadcrumbOnNavigateCallbackInvoker,
72 from_handle_fn: AzBreadcrumbOnNavigateCallback_createFromHostHandle,
73 extra_args: [ state: BreadcrumbState ],
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78#[repr(C)]
79pub struct Breadcrumb {
80 pub breadcrumb_state: BreadcrumbStateWrapper,
81 pub labels: StringVec,
83 pub container_style: CssPropertyWithConditionsVec,
85}
86
87#[derive(Debug, Default, Clone, PartialEq, Eq)]
88#[repr(C)]
89pub struct BreadcrumbStateWrapper {
90 pub inner: BreadcrumbState,
92 pub on_navigate: OptionBreadcrumbOnNavigate,
94}
95
96#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
98#[repr(C)]
99pub struct BreadcrumbState {
100 pub selected_index: usize,
102}
103
104const LINK_COLOR: ColorU = ColorU { r: 13, g: 110, b: 253, a: 255 };
107const CURRENT_COLOR: ColorU = ColorU { r: 73, g: 80, b: 87, a: 255 };
109const SEPARATOR_COLOR: ColorU = ColorU { r: 108, g: 117, b: 125, a: 255 };
111
112static BREADCRUMB_CONTAINER_STYLE: &[CssPropertyWithConditions] = &[
114 CssPropertyWithConditions::simple(CssProperty::const_display(LayoutDisplay::Flex)),
115 CssPropertyWithConditions::simple(CssProperty::const_flex_direction(LayoutFlexDirection::Row)),
116 CssPropertyWithConditions::simple(CssProperty::const_align_items(LayoutAlignItems::Center)),
117 CssPropertyWithConditions::simple(CssProperty::align_self(LayoutAlignSelf::Start)),
118 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
119 CssPropertyWithConditions::simple(CssProperty::const_font_size(StyleFontSize::const_px(14))),
120];
121
122static BREADCRUMB_ITEM_STYLE: &[CssPropertyWithConditions] = &[
126 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
127 CssPropertyWithConditions::simple(CssProperty::const_cursor(StyleCursor::Pointer)),
128 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
129 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
130 inner: LINK_COLOR,
131 })),
132];
133
134static BREADCRUMB_CURRENT_STYLE: &[CssPropertyWithConditions] = &[
136 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
137 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
138 CssPropertyWithConditions::simple(CssProperty::font_weight(StyleFontWeight::Bold)),
139 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
140 inner: CURRENT_COLOR,
141 })),
142];
143
144static BREADCRUMB_SEPARATOR_STYLE: &[CssPropertyWithConditions] = &[
146 CssPropertyWithConditions::simple(CssProperty::const_flex_grow(LayoutFlexGrow::const_new(0))),
147 CssPropertyWithConditions::simple(CssProperty::user_select(StyleUserSelect::None)),
148 CssPropertyWithConditions::simple(CssProperty::const_margin_left(LayoutMarginLeft::const_px(8))),
149 CssPropertyWithConditions::simple(CssProperty::const_margin_right(LayoutMarginRight::const_px(
150 8,
151 ))),
152 CssPropertyWithConditions::simple(CssProperty::const_text_color(StyleTextColor {
153 inner: SEPARATOR_COLOR,
154 })),
155];
156
157impl Breadcrumb {
158 #[must_use] pub fn create(labels: StringVec) -> Self {
160 Self {
161 breadcrumb_state: BreadcrumbStateWrapper::default(),
162 labels,
163 container_style: CssPropertyWithConditionsVec::from_const_slice(
164 BREADCRUMB_CONTAINER_STYLE,
165 ),
166 }
167 }
168
169 #[inline]
170 #[must_use] pub fn swap_with_default(&mut self) -> Self {
171 let mut s = Self::create(StringVec::from_const_slice(&[]));
172 core::mem::swap(&mut s, self);
173 s
174 }
175
176 #[inline]
177 pub fn set_on_navigate<C: Into<BreadcrumbOnNavigateCallback>>(
178 &mut self,
179 data: RefAny,
180 on_navigate: C,
181 ) {
182 self.breadcrumb_state.on_navigate = Some(BreadcrumbOnNavigate {
183 callback: on_navigate.into(),
184 refany: data,
185 })
186 .into();
187 }
188
189 #[inline]
190 #[must_use] pub fn with_on_navigate<C: Into<BreadcrumbOnNavigateCallback>>(
191 mut self,
192 data: RefAny,
193 on_navigate: C,
194 ) -> Self {
195 self.set_on_navigate(data, on_navigate);
196 self
197 }
198
199 #[must_use] pub fn dom(self) -> Dom {
200 use azul_core::{
201 callbacks::CoreCallback,
202 dom::{EventFilter, HoverEventFilter},
203 refany::OptionRefAny,
204 };
205
206 let count = self.labels.as_ref().len();
207
208 let state = RefAny::new(self.breadcrumb_state);
211
212 let mut children: Vec<Dom> = Vec::with_capacity(count.saturating_mul(2));
213 for (i, label) in self.labels.as_ref().iter().enumerate() {
214 let is_last = i + 1 == count;
215
216 if is_last {
217 children.push(
219 Dom::create_text(label.clone())
220 .with_ids_and_classes(IdOrClassVec::from_const_slice(
221 BREADCRUMB_CURRENT_CLASS,
222 ))
223 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
224 BREADCRUMB_CURRENT_STYLE,
225 )),
226 );
227 } else {
228 children.push(
230 Dom::create_text(label.clone())
231 .with_ids_and_classes(IdOrClassVec::from_const_slice(BREADCRUMB_ITEM_CLASS))
232 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
233 BREADCRUMB_ITEM_STYLE,
234 ))
235 .with_callbacks(
236 vec![CoreCallbackData {
237 event: EventFilter::Hover(HoverEventFilter::MouseUp),
238 callback: CoreCallback {
239 cb: on_crumb_click as usize,
240 ctx: OptionRefAny::None,
241 },
242 refany: state.clone(),
243 }]
244 .into(),
245 )
246 .with_tab_index(TabIndex::Auto),
247 );
248 children.push(
250 Dom::create_text(SEPARATOR_GLYPH)
251 .with_ids_and_classes(IdOrClassVec::from_const_slice(
252 BREADCRUMB_SEPARATOR_CLASS,
253 ))
254 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
255 BREADCRUMB_SEPARATOR_STYLE,
256 )),
257 );
258 }
259 }
260
261 Dom::create_div()
262 .with_ids_and_classes(IdOrClassVec::from_const_slice(BREADCRUMB_CLASS))
263 .with_css_props(self.container_style)
264 .with_children(children.into())
265 }
266}
267
268impl Default for Breadcrumb {
269 fn default() -> Self {
270 Self::create(StringVec::from_const_slice(&[]))
271 }
272}
273
274extern "C" fn on_crumb_click(mut data: RefAny, mut info: CallbackInfo) -> Update {
280 use azul_core::dom::DomNodeId;
281
282 let clicked = info.get_hit_node();
283 let Some(parent) = info.get_parent(clicked) else {
284 return Update::DoNothing;
285 };
286
287 let mut siblings: Vec<DomNodeId> = Vec::new();
289 let mut cur = info.get_first_child(parent);
290 while let Some(node) = cur {
291 siblings.push(node);
292 cur = info.get_next_sibling(node);
293 }
294
295 let Some(pos) = siblings.iter().position(|n| *n == clicked) else {
296 return Update::DoNothing;
297 };
298 let index = pos / 2;
300
301 let Some(mut bc) = data.downcast_mut::<BreadcrumbStateWrapper>() else {
302 return Update::DoNothing;
303 };
304 bc.inner.selected_index = index;
305 let inner = bc.inner;
306 let bc = &mut *bc;
307 match bc.on_navigate.as_mut() {
308 Some(BreadcrumbOnNavigate { callback, refany }) => (callback.cb)(refany.clone(), info, inner),
309 None => Update::DoNothing,
310 }
311}
312
313impl From<Breadcrumb> for Dom {
314 fn from(b: Breadcrumb) -> Self {
315 b.dom()
316 }
317}