1use azul_core::{
10 callbacks::{CoreCallback, CoreCallbackData, Update},
11 dom::{Dom, DomVec, EventFilter, HoverEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec},
12 refany::RefAny,
13};
14#[allow(clippy::wildcard_imports)] use azul_css::{
16 dynamic_selector::{CssPropertyWithConditions as Cond, CssPropertyWithConditionsVec},
17 props::{
18 basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, *},
19 layout::*,
20 property::CssProperty as P,
21 style::*,
22 },
23 *,
24};
25
26use azul_css::{impl_option, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_partialeq, impl_vec_mut};
27
28use crate::callbacks::{Callback, CallbackInfo};
29
30pub type RibbonOnTabClickCallbackType = extern "C" fn(RefAny, CallbackInfo, usize) -> Update;
34impl_widget_callback!(
35 RibbonOnTabClick, OptionRibbonOnTabClick,
36 RibbonOnTabClickCallback, RibbonOnTabClickCallbackType
37);
38
39azul_core::impl_managed_callback! {
40 wrapper: RibbonOnTabClickCallback,
41 info_ty: CallbackInfo,
42 return_ty: Update,
43 default_ret: Update::DoNothing,
44 invoker_static: RIBBON_ON_TAB_CLICK_INVOKER,
45 invoker_ty: AzRibbonOnTabClickCallbackInvoker,
46 thunk_fn: az_ribbon_on_tab_click_callback_thunk,
47 setter_fn: AzApp_setRibbonOnTabClickCallbackInvoker,
48 from_handle_fn: AzRibbonOnTabClickCallback_createFromHostHandle,
49 extra_args: [ tab_index: usize ],
50}
51
52const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
55const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
56const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
57 StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
58
59const WHITE: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
62const LIGHT_GRAY: ColorU = ColorU { r: 240, g: 240, b: 240, a: 255 };
63const BORDER_GRAY: ColorU = ColorU { r: 200, g: 200, b: 200, a: 255 };
64const TEXT_GRAY: ColorU = ColorU { r: 100, g: 100, b: 100, a: 255 };
65const ACTIVE_BLUE: ColorU = ColorU { r: 0, g: 114, b: 198, a: 255 };
66const BG_WHITE: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(WHITE)];
67const BG_LIGHT_GRAY: &[StyleBackgroundContent] = &[StyleBackgroundContent::Color(LIGHT_GRAY)];
68
69static RIBBON_CONTAINER_STYLE: &[Cond] = &[
70 Cond::simple(P::const_display(LayoutDisplay::Flex)),
71 Cond::simple(P::const_flex_direction(LayoutFlexDirection::Column)),
72 Cond::simple(P::const_font_family(SYSTEM_UI_FAMILY)),
73 Cond::simple(P::const_font_size(StyleFontSize::const_px(12))),
74];
75
76static TAB_BAR_STYLE: &[Cond] = &[
77 Cond::simple(P::const_display(LayoutDisplay::Flex)),
78 Cond::simple(P::const_flex_direction(LayoutFlexDirection::Row)),
79 Cond::simple(P::const_background_content(StyleBackgroundContentVec::from_const_slice(BG_LIGHT_GRAY))),
80 Cond::simple(P::const_border_bottom_width(LayoutBorderBottomWidth::const_px(1))),
81 Cond::simple(P::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })),
82 Cond::simple(P::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_GRAY })),
83];
84
85static TAB_INACTIVE_STYLE: &[Cond] = &[
86 Cond::simple(P::const_padding_left(LayoutPaddingLeft::const_px(12))),
87 Cond::simple(P::const_padding_right(LayoutPaddingRight::const_px(12))),
88 Cond::simple(P::const_padding_top(LayoutPaddingTop::const_px(6))),
89 Cond::simple(P::const_padding_bottom(LayoutPaddingBottom::const_px(6))),
90 Cond::simple(P::const_cursor(StyleCursor::Pointer)),
91 Cond::simple(P::const_text_color(StyleTextColor { inner: TEXT_GRAY })),
92];
93
94static TAB_ACTIVE_STYLE: &[Cond] = &[
95 Cond::simple(P::const_padding_left(LayoutPaddingLeft::const_px(12))),
96 Cond::simple(P::const_padding_right(LayoutPaddingRight::const_px(12))),
97 Cond::simple(P::const_padding_top(LayoutPaddingTop::const_px(6))),
98 Cond::simple(P::const_padding_bottom(LayoutPaddingBottom::const_px(6))),
99 Cond::simple(P::const_cursor(StyleCursor::Pointer)),
100 Cond::simple(P::const_background_content(StyleBackgroundContentVec::from_const_slice(BG_WHITE))),
101 Cond::simple(P::const_border_bottom_width(LayoutBorderBottomWidth::const_px(2))),
102 Cond::simple(P::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })),
103 Cond::simple(P::const_border_bottom_color(StyleBorderBottomColor { inner: ACTIVE_BLUE })),
104];
105
106static SECTIONS_CONTAINER_STYLE: &[Cond] = &[
107 Cond::simple(P::const_display(LayoutDisplay::Flex)),
108 Cond::simple(P::const_flex_direction(LayoutFlexDirection::Row)),
109 Cond::simple(P::const_flex_grow(LayoutFlexGrow::const_new(1))),
110 Cond::simple(P::const_background_content(StyleBackgroundContentVec::from_const_slice(BG_WHITE))),
111 Cond::simple(P::const_padding_top(LayoutPaddingTop::const_px(4))),
112 Cond::simple(P::const_padding_bottom(LayoutPaddingBottom::const_px(4))),
113 Cond::simple(P::const_padding_left(LayoutPaddingLeft::const_px(4))),
114 Cond::simple(P::const_padding_right(LayoutPaddingRight::const_px(4))),
115 Cond::simple(P::const_border_bottom_width(LayoutBorderBottomWidth::const_px(1))),
116 Cond::simple(P::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })),
117 Cond::simple(P::const_border_bottom_color(StyleBorderBottomColor { inner: BORDER_GRAY })),
118];
119
120static SECTION_STYLE: &[Cond] = &[
121 Cond::simple(P::const_display(LayoutDisplay::Flex)),
122 Cond::simple(P::const_flex_direction(LayoutFlexDirection::Column)),
123 Cond::simple(P::const_padding_left(LayoutPaddingLeft::const_px(6))),
124 Cond::simple(P::const_padding_right(LayoutPaddingRight::const_px(6))),
125 Cond::simple(P::const_border_right_width(LayoutBorderRightWidth::const_px(1))),
126 Cond::simple(P::const_border_right_style(StyleBorderRightStyle { inner: BorderStyle::Solid })),
127 Cond::simple(P::const_border_right_color(StyleBorderRightColor { inner: BORDER_GRAY })),
128];
129
130static SECTION_CONTENT_STYLE: &[Cond] = &[
131 Cond::simple(P::const_flex_grow(LayoutFlexGrow::const_new(1))),
132];
133
134static SECTION_TITLE_STYLE: &[Cond] = &[
135 Cond::simple(P::const_font_size(StyleFontSize::const_px(11))),
136 Cond::simple(P::const_text_color(StyleTextColor { inner: TEXT_GRAY })),
137 Cond::simple(P::const_text_align(StyleTextAlign::Center)),
138 Cond::simple(P::const_padding_top(LayoutPaddingTop::const_px(2))),
139];
140
141#[derive(Debug, Clone)]
143#[repr(C)]
144pub struct Ribbon {
145 pub tabs: RibbonTabVec,
147 pub active_tab: usize,
149 pub on_tab_click: OptionRibbonOnTabClick,
151}
152
153#[derive(Debug, Clone)]
155#[repr(C)]
156pub struct RibbonTab {
157 pub label: AzString,
159 pub sections: RibbonSectionVec,
161}
162
163#[derive(Debug, Clone)]
165#[repr(C)]
166pub struct RibbonSection {
167 pub title: AzString,
169 pub content: Dom,
171}
172
173impl_option!(RibbonSection, OptionRibbonSection, copy = false, [Debug, Clone]);
174impl_vec!(RibbonSection, RibbonSectionVec, RibbonSectionVecDestructor, RibbonSectionVecDestructorType, RibbonSectionVecSlice, OptionRibbonSection);
175impl_vec_clone!(RibbonSection, RibbonSectionVec, RibbonSectionVecDestructor);
176impl_vec_debug!(RibbonSection, RibbonSectionVec);
177impl_vec_mut!(RibbonSection, RibbonSectionVec);
178
179impl_option!(RibbonTab, OptionRibbonTab, copy = false, [Debug, Clone]);
180impl_vec!(RibbonTab, RibbonTabVec, RibbonTabVecDestructor, RibbonTabVecDestructorType, RibbonTabVecSlice, OptionRibbonTab);
181impl_vec_clone!(RibbonTab, RibbonTabVec, RibbonTabVecDestructor);
182impl_vec_debug!(RibbonTab, RibbonTabVec);
183impl_vec_mut!(RibbonTab, RibbonTabVec);
184
185impl RibbonTab {
186 #[must_use] pub const fn new(label: AzString) -> Self {
188 Self { label, sections: RibbonSectionVec::from_const_slice(&[]) }
189 }
190
191 pub fn add_section(&mut self, section: RibbonSection) {
193 self.sections.push(section);
194 }
195
196 #[must_use] pub fn with_section(mut self, section: RibbonSection) -> Self {
198 self.add_section(section);
199 self
200 }
201}
202
203impl RibbonSection {
204 #[must_use] pub const fn new(title: AzString, content: Dom) -> Self {
206 Self { title, content }
207 }
208}
209
210impl Ribbon {
211 #[must_use] pub fn new(tabs: RibbonTabVec) -> Self {
213 Self { tabs, active_tab: 0, on_tab_click: None.into() }
214 }
215
216 pub const fn set_active_tab(&mut self, index: usize) {
218 let max = self.tabs.len().saturating_sub(1);
219 self.active_tab = if index > max { max } else { index };
220 }
221
222 pub fn set_on_tab_click<C: Into<RibbonOnTabClickCallback>>(&mut self, data: RefAny, cb: C) {
224 self.on_tab_click = Some(RibbonOnTabClick {
225 callback: cb.into(), refany: data,
226 }).into();
227 }
228
229 #[must_use]
231 pub fn with_on_tab_click<C: Into<RibbonOnTabClickCallback>>(mut self, data: RefAny, cb: C) -> Self {
232 self.set_on_tab_click(data, cb);
233 self
234 }
235
236 #[must_use] pub fn dom(self) -> Dom {
238 let active_tab = self.active_tab;
239 let has_callback = self.on_tab_click.is_some();
240
241 let tab_items: Vec<Dom> = self.tabs.as_slice().iter().enumerate().map(|(idx, tab)| {
242 let style = if idx == active_tab { TAB_ACTIVE_STYLE } else { TAB_INACTIVE_STYLE };
243 let mut d = Dom::create_text(tab.label.clone())
244 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(style));
245 if has_callback {
246 d = d.with_callbacks(vec![CoreCallbackData {
247 event: EventFilter::Hover(HoverEventFilter::MouseUp),
248 callback: CoreCallback {
249 cb: on_ribbon_tab_click as usize,
250 ctx: azul_core::refany::OptionRefAny::None,
251 },
252 refany: RefAny::new(TabClickData {
253 tab_idx: idx, on_tab_click: self.on_tab_click.clone(),
254 }),
255 }].into());
256 }
257 d
258 }).collect();
259
260 let tab_bar = Dom::create_div()
261 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(TAB_BAR_STYLE))
262 .with_children(DomVec::from_vec(tab_items));
263
264 let sections_dom = if let Some(active) = self.tabs.into_library_owned_vec().into_iter().nth(active_tab) {
265 let items: Vec<Dom> = active.sections.into_library_owned_vec().into_iter().map(|s| {
266 let content = Dom::create_div()
267 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(SECTION_CONTENT_STYLE))
268 .with_children(DomVec::from_vec(vec![s.content]));
269 let title = Dom::create_text(s.title)
270 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(SECTION_TITLE_STYLE));
271 Dom::create_div()
272 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(SECTION_STYLE))
273 .with_children(DomVec::from_vec(vec![content, title]))
274 }).collect();
275 Dom::create_div()
276 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(SECTIONS_CONTAINER_STYLE))
277 .with_children(DomVec::from_vec(items))
278 } else {
279 Dom::create_div()
280 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(SECTIONS_CONTAINER_STYLE))
281 };
282
283 Dom::create_div()
284 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(RIBBON_CONTAINER_STYLE))
285 .with_ids_and_classes({
286 const CLS: &[IdOrClass] = &[Class(AzString::from_const_str("__azul-native-ribbon"))];
287 IdOrClassVec::from_const_slice(CLS)
288 })
289 .with_children(DomVec::from_vec(vec![tab_bar, sections_dom]))
290 }
291}
292
293struct TabClickData {
294 tab_idx: usize,
295 on_tab_click: OptionRibbonOnTabClick,
296}
297
298extern "C" fn on_ribbon_tab_click(mut refany: RefAny, info: CallbackInfo) -> Update {
299 let Some(mut data) = refany.downcast_mut::<TabClickData>() else {
300 return Update::DoNothing;
301 };
302 let idx = data.tab_idx;
303 match data.on_tab_click.as_mut() {
304 Some(RibbonOnTabClick { refany, callback }) => {
305 (callback.cb)(refany.clone(), info, idx)
306 }
307 None => Update::DoNothing,
308 }
309}
310
311impl From<Ribbon> for Dom {
312 fn from(r: Ribbon) -> Self { r.dom() }
313}