1
//! Microsoft Office-style title band with a Quick Access Toolbar
2
//! (the Office-2013-era look look by default).
3
//!
4
//! Models the top chrome band of an Office document window:
5
//!
6
//! ```text
7
//! QuickAccessBar ─ leading slot            (app logo, any user Dom)
8
//!                ─ quick-access actions    QuickAccessAction (save / undo / redo)
9
//!                ─ customize arrow         ("▾" menu glyph, optional)
10
//!                ─ window title            (centered, "Document1 - AzWriter")
11
//!                ─ trailing actions        QuickAccessAction (help, ribbon options)
12
//!                ─ window buttons          minimize / maximize / close
13
//! ```
14
//!
15
//! Buttons are not re-implemented: every action and window button expands
16
//! to the existing [`super::button::Button`] widget with title-band part
17
//! styles injected through `Button`'s public style fields (the same
18
//! composition rule the ribbon uses). Icons resolve through the registered
19
//! icon provider (Material Icons by default): "save", "undo", "redo",
20
//! "help_outline", "minimize", "crop_square", "close".
21
//!
22
//! This widget draws WINDOW CHROME AS DOM — pair it with borderless window
23
//! decorations, or use [`super::titlebar::Titlebar`] when the OS should
24
//! draw its native caption instead. Window-button clicks are forwarded to
25
//! application callbacks; the band never calls `modify_window_state`
26
//! itself, so it stays inert in mockups and screenshot harnesses.
27
//!
28
//! All visual parts are exposed on [`QuickAccessStyle`] (defaults =
29
//! the Office-2013-era look look, [`QuickAccessStyle::office_2013`]); replace any field to
30
//! re-theme without touching widget code. There is no behavior struct: the
31
//! band has no self-driven chrome interactions.
32

            
33
use azul_core::{
34
    dom::{Dom, DomVec, IdOrClass, IdOrClass::Class, IdOrClassVec},
35
    refany::RefAny,
36
};
37
#[allow(clippy::wildcard_imports)] // widget/render module pulls in the css property/value types it builds with
38
use azul_css::{
39
    dynamic_selector::{CssPropertyWithConditions as Cond, CssPropertyWithConditionsVec},
40
    props::{
41
        basic::{color::ColorU, font::{StyleFontFamily, StyleFontFamilyVec}, *},
42
        layout::*,
43
        property::CssProperty as P,
44
        style::*,
45
    },
46
    *,
47
};
48

            
49
use azul_css::{impl_option, impl_vec, impl_vec_clone, impl_vec_debug, impl_vec_mut};
50

            
51
use super::button::{Button, OptionButtonOnClick};
52

            
53
// -- Font --
54

            
55
const SYSTEM_UI_STR: AzString = AzString::from_const_str("system:ui");
56
const SYSTEM_UI_FAMILIES: &[StyleFontFamily] = &[StyleFontFamily::System(SYSTEM_UI_STR)];
57
const SYSTEM_UI_FAMILY: StyleFontFamilyVec =
58
    StyleFontFamilyVec::from_const_slice(SYSTEM_UI_FAMILIES);
59

            
60
// -- the Office-2013-era look palette (seeds QuickAccessTheme::office_2013) --
61

            
62
const WHITE: ColorU = ColorU { r: 255, g: 255, b: 255, a: 255 };
63
const TRANSPARENT: ColorU = ColorU { r: 0, g: 0, b: 0, a: 0 };
64
/// Title text gray (#5D5D5D).
65
const W13_TITLE_TEXT: ColorU = ColorU { r: 93, g: 93, b: 93, a: 255 };
66
/// Action glyph gray (#6A6A6A).
67
const W13_ICON_GRAY: ColorU = ColorU { r: 106, g: 106, b: 106, a: 255 };
68
/// Hover fill on band controls (#E5E5E5).
69
const W13_HOVER_BG: ColorU = ColorU { r: 229, g: 229, b: 229, a: 255 };
70
/// Pressed fill (#CCCCCC).
71
const W13_PRESSED_BG: ColorU = ColorU { r: 204, g: 204, b: 204, a: 255 };
72
/// Close button hover fill (#E81123, the Windows caption red).
73
const W13_CLOSE_HOVER: ColorU = ColorU { r: 232, g: 17, b: 35, a: 255 };
74

            
75
// -- Metrics (the Office-2013-era look, logical px) --
76

            
77
/// Band height.
78
const BAR_HEIGHT: isize = 28;
79
/// Quick-access glyph size.
80
const QAT_ICON_PX: isize = 15;
81
/// Window-button glyph size.
82
const WIN_ICON_PX: isize = 14;
83
/// Width of one quick-access button.
84
const QAT_BUTTON_W: isize = 26;
85
/// Width of one window button (office-2013: wide flat caption buttons).
86
const WIN_BUTTON_W: isize = 34;
87
/// Title text size.
88
const TITLE_PX: isize = 12;
89

            
90
// -- Theme --
91

            
92
/// Color palette from which a full [`QuickAccessStyle`] is derived via
93
/// [`QuickAccessStyle::from_theme`]. All fields are plain colors, so themes
94
/// are trivially constructible over FFI. Preset:
95
/// [`QuickAccessTheme::office_2013`] (the default).
96
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97
#[repr(C)]
98
pub struct QuickAccessTheme {
99
    /// Band fill (office-2013: white).
100
    pub bg: ColorU,
101
    /// Title text color.
102
    pub text: ColorU,
103
    /// Action and window-button glyph color.
104
    pub icon: ColorU,
105
    /// Hover fill on band controls.
106
    pub hover_bg: ColorU,
107
    /// Pressed fill on band controls.
108
    pub pressed_bg: ColorU,
109
    /// Close button hover fill (caption red).
110
    pub close_hover_bg: ColorU,
111
    /// Close button glyph color while hovered.
112
    pub close_hover_icon: ColorU,
113
}
114

            
115
impl QuickAccessTheme {
116
    /// The the Office-2013-era look palette: white band, gray glyphs, red close hover.
117
    #[must_use]
118
10
    pub const fn office_2013() -> Self {
119
10
        Self {
120
10
            bg: WHITE,
121
10
            text: W13_TITLE_TEXT,
122
10
            icon: W13_ICON_GRAY,
123
10
            hover_bg: W13_HOVER_BG,
124
10
            pressed_bg: W13_PRESSED_BG,
125
10
            close_hover_bg: W13_CLOSE_HOVER,
126
10
            close_hover_icon: WHITE,
127
10
        }
128
10
    }
129
}
130

            
131
impl Default for QuickAccessTheme {
132
    fn default() -> Self {
133
        Self::office_2013()
134
    }
135
}
136

            
137
// -- Theme -> property-list builders --
138

            
139
80
fn bg_vec(c: ColorU) -> StyleBackgroundContentVec {
140
80
    StyleBackgroundContentVec::from_vec(vec![StyleBackgroundContent::Color(c)])
141
80
}
142

            
143
30
fn cond_bg(c: ColorU) -> Cond {
144
30
    Cond::simple(P::const_background_content(bg_vec(c)))
145
30
}
146

            
147
30
fn cond_bg_hover(c: ColorU) -> Cond {
148
30
    Cond::on_hover(P::const_background_content(bg_vec(c)))
149
30
}
150

            
151
20
fn cond_bg_active(c: ColorU) -> Cond {
152
20
    Cond::on_active(P::const_background_content(bg_vec(c)))
153
20
}
154

            
155
40
const fn cond_text_color(c: ColorU) -> Cond {
156
40
    Cond::simple(P::const_text_color(StyleTextColor { inner: c }))
157
40
}
158

            
159
30
const fn cond_border_box() -> Cond {
160
30
    Cond::simple(P::const_box_sizing(LayoutBoxSizing::BorderBox))
161
30
}
162

            
163
40
fn push_row_center(v: &mut Vec<Cond>) {
164
40
    v.push(Cond::simple(P::const_display(LayoutDisplay::Flex)));
165
40
    v.push(Cond::simple(P::const_flex_direction(LayoutFlexDirection::Row)));
166
40
    v.push(Cond::simple(P::const_align_items(LayoutAlignItems::Center)));
167
40
    v.push(Cond::simple(P::const_flex_grow(LayoutFlexGrow::const_new(0))));
168
40
    v.push(Cond::simple(P::const_flex_shrink(LayoutFlexShrink { inner: FloatValue::const_new(0) })));
169
40
}
170

            
171
/// Flat, hover-highlighted button chassis shared by every band control.
172
/// The explicit TRANSPARENT border overrides the [`Button`] widget's
173
/// default frame (the classic office-suite band controls are frameless until hovered).
174
20
fn push_flat_button(v: &mut Vec<Cond>, t: &QuickAccessTheme) {
175
20
    v.push(cond_border_box());
176
20
    v.push(Cond::simple(P::const_cursor(StyleCursor::Default)));
177
20
    v.push(Cond::simple(P::user_select(StyleUserSelect::None)));
178
20
    v.push(cond_bg(TRANSPARENT));
179
20
    push_box_border(v, TRANSPARENT);
180
20
    v.push(cond_bg_hover(t.hover_bg));
181
20
    v.push(cond_bg_active(t.pressed_bg));
182
20
}
183

            
184
/// 1px solid border on all four sides in the given color.
185
20
fn push_box_border(v: &mut Vec<Cond>, c: ColorU) {
186
20
    v.push(Cond::simple(P::const_border_top_width(LayoutBorderTopWidth::const_px(1))));
187
20
    v.push(Cond::simple(P::const_border_left_width(LayoutBorderLeftWidth::const_px(1))));
188
20
    v.push(Cond::simple(P::const_border_right_width(LayoutBorderRightWidth::const_px(1))));
189
20
    v.push(Cond::simple(P::const_border_bottom_width(LayoutBorderBottomWidth::const_px(1))));
190
20
    v.push(Cond::simple(P::const_border_top_style(StyleBorderTopStyle { inner: BorderStyle::Solid })));
191
20
    v.push(Cond::simple(P::const_border_left_style(StyleBorderLeftStyle { inner: BorderStyle::Solid })));
192
20
    v.push(Cond::simple(P::const_border_right_style(StyleBorderRightStyle { inner: BorderStyle::Solid })));
193
20
    v.push(Cond::simple(P::const_border_bottom_style(StyleBorderBottomStyle { inner: BorderStyle::Solid })));
194
20
    v.push(Cond::simple(P::const_border_top_color(StyleBorderTopColor { inner: c })));
195
20
    v.push(Cond::simple(P::const_border_left_color(StyleBorderLeftColor { inner: c })));
196
20
    v.push(Cond::simple(P::const_border_right_color(StyleBorderRightColor { inner: c })));
197
20
    v.push(Cond::simple(P::const_border_bottom_color(StyleBorderBottomColor { inner: c })));
198
20
}
199

            
200
10
fn theme_bar(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
201
10
    let mut v = Vec::new();
202
10
    push_row_center(&mut v);
203
10
    v.push(cond_border_box());
204
10
    v.push(Cond::simple(P::const_height(LayoutHeight::const_px(BAR_HEIGHT))));
205
10
    v.push(Cond::simple(P::const_font_family(SYSTEM_UI_FAMILY)));
206
10
    v.push(Cond::simple(P::const_font_size(StyleFontSize::const_px(TITLE_PX))));
207
10
    v.push(cond_bg(t.bg));
208
10
    v.push(Cond::simple(P::const_padding_left(LayoutPaddingLeft::const_px(8))));
209
10
    CssPropertyWithConditionsVec::from_vec(v)
210
10
}
211

            
212
10
fn theme_leading(_t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
213
10
    let mut v = Vec::new();
214
10
    push_row_center(&mut v);
215
10
    v.push(Cond::simple(P::const_margin_right(LayoutMarginRight::const_px(4))));
216
10
    CssPropertyWithConditionsVec::from_vec(v)
217
10
}
218

            
219
10
fn theme_action_button(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
220
10
    let mut v = Vec::new();
221
10
    push_row_center(&mut v);
222
10
    push_flat_button(&mut v, t);
223
10
    v.push(Cond::simple(P::const_justify_content(LayoutJustifyContent::Center)));
224
10
    v.push(Cond::simple(P::const_width(LayoutWidth::const_px(QAT_BUTTON_W))));
225
10
    v.push(Cond::simple(P::const_height(LayoutHeight::const_px(BAR_HEIGHT - 4))));
226
10
    CssPropertyWithConditionsVec::from_vec(v)
227
10
}
228

            
229
10
fn theme_action_icon(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
230
10
    CssPropertyWithConditionsVec::from_vec(vec![
231
10
        Cond::simple(P::const_font_size(StyleFontSize::const_px(QAT_ICON_PX))),
232
10
        cond_text_color(t.icon),
233
    ])
234
10
}
235

            
236
/// The small "customize quick access toolbar" chevron after the actions.
237
10
fn theme_menu_arrow(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
238
10
    CssPropertyWithConditionsVec::from_vec(vec![
239
10
        Cond::simple(P::const_font_size(StyleFontSize::const_px(12))),
240
10
        cond_text_color(t.icon),
241
10
        Cond::simple(P::const_margin_left(LayoutMarginLeft::const_px(1))),
242
10
        Cond::simple(P::const_margin_right(LayoutMarginRight::const_px(4))),
243
    ])
244
10
}
245

            
246
10
fn theme_title(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
247
10
    CssPropertyWithConditionsVec::from_vec(vec![
248
10
        Cond::simple(P::const_flex_grow(LayoutFlexGrow::const_new(1))),
249
10
        Cond::simple(P::const_text_align(StyleTextAlign::Center)),
250
10
        Cond::simple(P::const_font_size(StyleFontSize::const_px(TITLE_PX))),
251
10
        cond_text_color(t.text),
252
10
        Cond::simple(P::user_select(StyleUserSelect::None)),
253
    ])
254
10
}
255

            
256
10
fn theme_window_button(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
257
10
    let mut v = Vec::new();
258
10
    push_row_center(&mut v);
259
10
    push_flat_button(&mut v, t);
260
10
    v.push(Cond::simple(P::const_justify_content(LayoutJustifyContent::Center)));
261
10
    v.push(Cond::simple(P::const_width(LayoutWidth::const_px(WIN_BUTTON_W))));
262
10
    v.push(Cond::simple(P::const_height(LayoutHeight::const_px(BAR_HEIGHT))));
263
10
    CssPropertyWithConditionsVec::from_vec(v)
264
10
}
265

            
266
10
fn theme_window_icon(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
267
10
    CssPropertyWithConditionsVec::from_vec(vec![
268
10
        Cond::simple(P::const_font_size(StyleFontSize::const_px(WIN_ICON_PX))),
269
10
        cond_text_color(t.icon),
270
    ])
271
10
}
272

            
273
/// APPENDED to the close button: the caption-red hover.
274
10
fn theme_close_button(t: &QuickAccessTheme) -> CssPropertyWithConditionsVec {
275
10
    CssPropertyWithConditionsVec::from_vec(vec![cond_bg_hover(t.close_hover_bg)])
276
10
}
277

            
278
// -- Style --
279

            
280
/// All part styles of the title band. Every part defaults to the the Office-2013-era look
281
/// look; replace any field for finer control (the same override API as
282
/// [`super::ribbon::RibbonStyle`]).
283
#[derive(Debug, Clone, PartialEq, Eq)]
284
#[repr(C)]
285
pub struct QuickAccessStyle {
286
    /// The palette this style bundle was derived from. Kept for consumers
287
    /// deriving matching custom parts.
288
    pub theme: QuickAccessTheme,
289
    /// The band itself (horizontal row).
290
    pub bar_style: CssPropertyWithConditionsVec,
291
    /// Wrapper around the leading slot.
292
    pub leading_style: CssPropertyWithConditionsVec,
293
    /// Container style injected into one quick-access [`Button`].
294
    pub action_button_style: CssPropertyWithConditionsVec,
295
    /// Icon style injected into the quick-access [`Button`]s.
296
    pub action_icon_style: CssPropertyWithConditionsVec,
297
    /// The customize chevron after the quick-access actions.
298
    pub menu_arrow_style: CssPropertyWithConditionsVec,
299
    /// The centered window title.
300
    pub title_style: CssPropertyWithConditionsVec,
301
    /// Container style injected into the window [`Button`]s.
302
    pub window_button_style: CssPropertyWithConditionsVec,
303
    /// Icon style injected into the window [`Button`]s.
304
    pub window_icon_style: CssPropertyWithConditionsVec,
305
    /// APPENDED to the close [`Button`] (caption-red hover).
306
    pub close_button_style: CssPropertyWithConditionsVec,
307
}
308

            
309
impl QuickAccessStyle {
310
    /// The the Office-2013-era look look (white band, gray glyphs) - the default.
311
    #[must_use]
312
10
    pub fn office_2013() -> Self {
313
10
        Self::from_theme(QuickAccessTheme::office_2013())
314
10
    }
315

            
316
    /// Derives every part style from the given palette.
317
    #[must_use]
318
10
    pub fn from_theme(theme: QuickAccessTheme) -> Self {
319
10
        let t = &theme;
320
10
        Self {
321
10
            theme,
322
10
            bar_style: theme_bar(t),
323
10
            leading_style: theme_leading(t),
324
10
            action_button_style: theme_action_button(t),
325
10
            action_icon_style: theme_action_icon(t),
326
10
            menu_arrow_style: theme_menu_arrow(t),
327
10
            title_style: theme_title(t),
328
10
            window_button_style: theme_window_button(t),
329
10
            window_icon_style: theme_window_icon(t),
330
10
            close_button_style: theme_close_button(t),
331
10
        }
332
10
    }
333
}
334

            
335
impl Default for QuickAccessStyle {
336
1
    fn default() -> Self {
337
1
        Self::office_2013()
338
1
    }
339
}
340

            
341
// -- Data model --
342

            
343
/// One icon action on the band (quick-access or trailing).
344
#[derive(Debug, Clone)]
345
#[repr(C)]
346
pub struct QuickAccessAction {
347
    /// Icon name, resolved through the registered icon provider.
348
    pub icon: AzString,
349
    /// Optional click handler; without one the action is inert.
350
    pub on_click: OptionButtonOnClick,
351
}
352

            
353
impl QuickAccessAction {
354
    /// Creates an inert action with the given icon name.
355
    #[must_use]
356
8
    pub fn new(icon: AzString) -> Self {
357
8
        Self { icon, on_click: None.into() }
358
8
    }
359

            
360
    /// Sets the click callback.
361
    pub fn set_on_click<C: Into<super::button::ButtonOnClickCallback>>(
362
        &mut self,
363
        data: RefAny,
364
        on_click: C,
365
    ) {
366
        self.on_click = Some(super::button::ButtonOnClick {
367
            refany: data,
368
            callback: on_click.into(),
369
        })
370
        .into();
371
    }
372

            
373
    /// Builder method: sets the click callback and returns `self`.
374
    #[must_use]
375
    pub fn with_on_click<C: Into<super::button::ButtonOnClickCallback>>(
376
        mut self,
377
        data: RefAny,
378
        on_click: C,
379
    ) -> Self {
380
        self.set_on_click(data, on_click);
381
        self
382
    }
383
}
384

            
385
impl_option!(
386
    QuickAccessAction,
387
    OptionQuickAccessAction,
388
    copy = false,
389
    [Debug, Clone]
390
);
391
impl_vec!(
392
    QuickAccessAction,
393
    QuickAccessActionVec,
394
    QuickAccessActionVecDestructor,
395
    QuickAccessActionVecDestructorType,
396
    QuickAccessActionVecSlice,
397
    OptionQuickAccessAction
398
);
399
impl_vec_clone!(QuickAccessAction, QuickAccessActionVec, QuickAccessActionVecDestructor);
400
impl_vec_debug!(QuickAccessAction, QuickAccessActionVec);
401
impl_vec_mut!(QuickAccessAction, QuickAccessActionVec);
402

            
403
/// Top-level title band: leading slot, quick-access actions, customize
404
/// arrow, centered title, trailing actions and the window buttons.
405
#[derive(Debug, Clone)]
406
#[repr(C)]
407
pub struct QuickAccessBar {
408
    /// Optional leading content (office-2013: the app logo square).
409
    pub leading: azul_core::dom::OptionDom,
410
    /// Quick-access actions (office-2013: save / undo / redo).
411
    pub actions: QuickAccessActionVec,
412
    /// Renders the "customize quick access toolbar" chevron.
413
    pub show_menu_arrow: bool,
414
    /// The centered window title ("Document1 - `AzWriter`").
415
    pub title: AzString,
416
    /// Actions between the title and the window buttons (office-2013: help,
417
    /// ribbon display options).
418
    pub trailing_actions: QuickAccessActionVec,
419
    /// Renders the minimize window button.
420
    pub show_minimize: bool,
421
    /// Renders the maximize/restore window button.
422
    pub show_maximize: bool,
423
    /// Renders the close window button.
424
    pub show_close: bool,
425
    /// Optional minimize handler.
426
    pub on_minimize: OptionButtonOnClick,
427
    /// Optional maximize/restore handler.
428
    pub on_maximize: OptionButtonOnClick,
429
    /// Optional close handler.
430
    pub on_close: OptionButtonOnClick,
431
    /// All part styles (defaults to the the Office-2013-era look look).
432
    pub style: QuickAccessStyle,
433
}
434

            
435
// -- CSS classes --
436

            
437
static CLS_QAB: &[IdOrClass] =
438
    &[Class(AzString::from_const_str("__azul-native-quick-access"))];
439
static CLS_LEADING: &[IdOrClass] =
440
    &[Class(AzString::from_const_str("__azul-native-quick-access-leading"))];
441
static CLS_TITLE: &[IdOrClass] =
442
    &[Class(AzString::from_const_str("__azul-native-quick-access-title"))];
443

            
444
// -- Constructors / builders --
445

            
446
impl QuickAccessBar {
447
    /// Creates a band with the given title, no actions and all three
448
    /// window buttons, in the the Office-2013-era look style.
449
    #[must_use]
450
7
    pub fn new(title: AzString) -> Self {
451
7
        Self {
452
7
            leading: None.into(),
453
7
            actions: QuickAccessActionVec::from_vec(Vec::new()),
454
7
            show_menu_arrow: false,
455
7
            title,
456
7
            trailing_actions: QuickAccessActionVec::from_vec(Vec::new()),
457
7
            show_minimize: true,
458
7
            show_maximize: true,
459
7
            show_close: true,
460
7
            on_minimize: None.into(),
461
7
            on_maximize: None.into(),
462
7
            on_close: None.into(),
463
7
            style: QuickAccessStyle::office_2013(),
464
7
        }
465
7
    }
466

            
467
    /// The the Office-2013-era look band: save / undo / redo quick-access actions (inert
468
    /// until callbacks are set), the customize chevron, and help before the
469
    /// window buttons.
470
    #[must_use]
471
2
    pub fn office_2013(title: AzString) -> Self {
472
2
        let mut band = Self::new(title);
473
2
        band.actions = QuickAccessActionVec::from_vec(vec![
474
2
            QuickAccessAction::new(AzString::from_const_str("save")),
475
2
            QuickAccessAction::new(AzString::from_const_str("undo")),
476
2
            QuickAccessAction::new(AzString::from_const_str("redo")),
477
        ]);
478
2
        band.show_menu_arrow = true;
479
2
        band.trailing_actions = QuickAccessActionVec::from_vec(vec![
480
2
            QuickAccessAction::new(AzString::from_const_str("help_outline")),
481
        ]);
482
2
        band
483
2
    }
484

            
485
    /// Builder method: sets the leading content.
486
    #[must_use]
487
1
    pub fn with_leading(mut self, leading: Dom) -> Self {
488
1
        self.leading = Some(leading).into();
489
1
        self
490
1
    }
491

            
492
    /// Builder method: replaces the quick-access actions.
493
    #[must_use]
494
    pub fn with_actions(mut self, actions: QuickAccessActionVec) -> Self {
495
        self.actions = actions;
496
        self
497
    }
498

            
499
    /// Builder method: replaces the trailing actions.
500
    #[must_use]
501
    pub fn with_trailing_actions(mut self, trailing_actions: QuickAccessActionVec) -> Self {
502
        self.trailing_actions = trailing_actions;
503
        self
504
    }
505

            
506
    /// Builder method: replaces the style bundle.
507
    #[must_use]
508
    pub fn with_style(mut self, style: QuickAccessStyle) -> Self {
509
        self.style = style;
510
        self
511
    }
512

            
513
    /// Renders the band.
514
    #[must_use]
515
5
    pub fn dom(self) -> Dom {
516
        let Self {
517
5
            leading,
518
5
            actions,
519
5
            show_menu_arrow,
520
5
            title,
521
5
            trailing_actions,
522
5
            show_minimize,
523
5
            show_maximize,
524
5
            show_close,
525
5
            on_minimize,
526
5
            on_maximize,
527
5
            on_close,
528
5
            style,
529
5
        } = self;
530

            
531
5
        let mut children: Vec<Dom> = Vec::with_capacity(actions.len() + 8);
532

            
533
5
        if let Some(lead) = leading.into_option() {
534
1
            children.push(
535
1
                Dom::create_div()
536
1
                    .with_ids_and_classes(IdOrClassVec::from_const_slice(CLS_LEADING))
537
1
                    .with_css_props(style.leading_style.clone())
538
1
                    .with_children(DomVec::from_vec(vec![lead])),
539
1
            );
540
4
        }
541

            
542
5
        for action in actions.into_library_owned_vec() {
543
3
            children.push(action_button(action, &style.action_button_style, &style));
544
3
        }
545

            
546
5
        if show_menu_arrow {
547
1
            children.push(
548
1
                Dom::create_icon(AzString::from_const_str("arrow_drop_down"))
549
1
                    .with_css_props(style.menu_arrow_style.clone()),
550
1
            );
551
4
        }
552

            
553
5
        children.push(
554
5
            Dom::create_p_with_text(title)
555
5
                .with_ids_and_classes(IdOrClassVec::from_const_slice(CLS_TITLE))
556
5
                .with_css_props(style.title_style.clone()),
557
        );
558

            
559
5
        for action in trailing_actions.into_library_owned_vec() {
560
1
            children.push(action_button(action, &style.window_button_style, &style));
561
1
        }
562

            
563
5
        if show_minimize {
564
4
            children.push(window_button(
565
4
                AzString::from_const_str("minimize"),
566
4
                style.window_button_style.clone(),
567
4
                &style,
568
4
                on_minimize,
569
4
            ));
570
4
        }
571
5
        if show_maximize {
572
4
            children.push(window_button(
573
4
                AzString::from_const_str("crop_square"),
574
4
                style.window_button_style.clone(),
575
4
                &style,
576
4
                on_maximize,
577
4
            ));
578
4
        }
579
5
        if show_close {
580
4
            children.push(window_button(
581
4
                AzString::from_const_str("close"),
582
4
                merged_style(&style.window_button_style, &style.close_button_style),
583
4
                &style,
584
4
                on_close,
585
4
            ));
586
4
        }
587

            
588
5
        Dom::create_div()
589
5
            .with_ids_and_classes(IdOrClassVec::from_const_slice(CLS_QAB))
590
5
            .with_css_props(style.bar_style)
591
5
            .with_children(DomVec::from_vec(children))
592
5
    }
593
}
594

            
595
impl Default for QuickAccessBar {
596
    fn default() -> Self {
597
        Self::new(AzString::from_const_str(""))
598
    }
599
}
600

            
601
impl From<QuickAccessBar> for Dom {
602
    fn from(q: QuickAccessBar) -> Self {
603
        q.dom()
604
    }
605
}
606

            
607
// -- DOM builders --
608

            
609
4
fn merged_style(
610
4
    base: &CssPropertyWithConditionsVec,
611
4
    extra: &CssPropertyWithConditionsVec,
612
4
) -> CssPropertyWithConditionsVec {
613
4
    if extra.as_ref().is_empty() {
614
        return base.clone();
615
4
    }
616
4
    let mut v: Vec<Cond> = base.as_ref().to_vec();
617
4
    v.extend_from_slice(extra.as_ref());
618
4
    CssPropertyWithConditionsVec::from_vec(v)
619
4
}
620

            
621
/// Expands one action to the existing [`Button`] widget with the given
622
/// container style injected.
623
4
fn action_button(
624
4
    action: QuickAccessAction,
625
4
    container: &CssPropertyWithConditionsVec,
626
4
    style: &QuickAccessStyle,
627
4
) -> Dom {
628
4
    let mut b = Button::create(AzString::from_const_str(""));
629
4
    b.icon = action.icon;
630
4
    b.container_style = container.clone();
631
4
    b.icon_style = style.action_icon_style.clone();
632
4
    b.on_click = action.on_click;
633
4
    b.dom()
634
4
}
635

            
636
12
fn window_button(
637
12
    icon: AzString,
638
12
    container: CssPropertyWithConditionsVec,
639
12
    style: &QuickAccessStyle,
640
12
    on_click: OptionButtonOnClick,
641
12
) -> Dom {
642
12
    let mut b = Button::create(AzString::from_const_str(""));
643
12
    b.icon = icon;
644
12
    b.container_style = container;
645
12
    b.icon_style = style.window_icon_style.clone();
646
12
    b.on_click = on_click;
647
12
    b.dom()
648
12
}
649

            
650
#[cfg(test)]
651
mod tests {
652
    use super::*;
653

            
654
    // ------------------------------------------------------------------
655
    // Constructors and invariants
656
    // ------------------------------------------------------------------
657

            
658
    #[test]
659
1
    fn quick_access_new_has_no_actions_and_all_window_buttons() {
660
1
        let q = QuickAccessBar::new(AzString::from("t"));
661
1
        assert_eq!(q.actions.len(), 0);
662
1
        assert_eq!(q.trailing_actions.len(), 0);
663
1
        assert!(!q.show_menu_arrow);
664
1
        assert!(q.show_minimize && q.show_maximize && q.show_close);
665
1
        assert_eq!(q.style, QuickAccessStyle::office_2013());
666
1
    }
667

            
668
    #[test]
669
1
    fn quick_access_office_2013_has_save_undo_redo_and_help() {
670
1
        let q = QuickAccessBar::office_2013(AzString::from("Document1 - AzWriter"));
671
3
        let icons: Vec<&str> = q.actions.as_slice().iter().map(|a| a.icon.as_str()).collect();
672
1
        assert_eq!(icons, ["save", "undo", "redo"]);
673
1
        assert!(q.show_menu_arrow);
674
1
        assert_eq!(q.trailing_actions.len(), 1);
675
1
    }
676

            
677
    #[test]
678
1
    fn quick_access_style_default_is_office_2013() {
679
1
        assert_eq!(QuickAccessStyle::default(), QuickAccessStyle::office_2013());
680
1
    }
681

            
682
    // ------------------------------------------------------------------
683
    // DOM shape
684
    // ------------------------------------------------------------------
685

            
686
    #[test]
687
1
    fn dom_renders_actions_arrow_title_trailing_and_window_buttons_in_order() {
688
1
        let dom = QuickAccessBar::office_2013(AzString::from("t")).dom();
689
        // 3 actions + arrow + title + 1 trailing + min + max + close
690
1
        assert_eq!(dom.children.as_ref().len(), 9);
691
1
    }
692

            
693
    #[test]
694
1
    fn dom_without_window_buttons_renders_title_only() {
695
1
        let mut q = QuickAccessBar::new(AzString::from("t"));
696
1
        q.show_minimize = false;
697
1
        q.show_maximize = false;
698
1
        q.show_close = false;
699
1
        let dom = q.dom();
700
1
        assert_eq!(dom.children.as_ref().len(), 1);
701
1
    }
702

            
703
    #[test]
704
1
    fn dom_wraps_the_leading_slot_first() {
705
1
        let q = QuickAccessBar::new(AzString::from("t")).with_leading(Dom::create_div());
706
1
        let dom = q.dom();
707
1
        let first = &dom.children.as_ref()[0];
708
1
        let classes = first.root.get_ids_and_classes();
709
1
        assert!(classes.as_ref().iter().any(|c| match c {
710
1
            Class(s) => s.as_str().contains("quick-access-leading"),
711
            IdOrClass::Id(_) => false,
712
1
        }));
713
1
    }
714
}