rat_widget/
toolbar.rs

1//! **EXPERIMENTAL** Toolbar with Buttons, Checkbox and Choices.
2
3use crate::_private::NonExhaustive;
4use crate::button::{Button, ButtonState, ButtonStyle};
5use crate::checkbox::{Checkbox, CheckboxState, CheckboxStyle};
6use crate::choice::{Choice, ChoicePopup, ChoiceSelect, ChoiceState, ChoiceStyle, ChoiceWidget};
7use crate::event::{ButtonOutcome, CheckOutcome, ChoiceOutcome};
8use crate::paired::{PairSplit, Paired, PairedState, PairedWidget};
9use crossterm::event::{Event, KeyEvent, KeyEventKind};
10use rat_event::{ConsumedEvent, HandleEvent, Outcome, Popup, Regular, event_flow};
11use rat_focus::{Focus, FocusBuilder, FocusFlag, HasFocus, Navigation};
12use rat_reloc::RelocatableState;
13use ratatui::buffer::Buffer;
14use ratatui::layout::Rect;
15use ratatui::prelude::BlockExt;
16use ratatui::style::Style;
17use ratatui::text::{Line, Span};
18use ratatui::widgets::{Block, StatefulWidget, Widget};
19use std::borrow::Cow;
20
21#[derive(Debug)]
22enum Tool<'a> {
23    CollapsedButtons(Cow<'a, str>),
24    BasicButton(Cow<'a, str>, Cow<'a, str>, bool),
25    BasicCheckbox(Cow<'a, str>, Cow<'a, str>, bool),
26    BasicChoice(Cow<'a, str>, Vec<Line<'a>>, usize),
27    Text(Line<'a>),
28}
29
30/// States for the different toolbar-widgets.
31#[derive(Debug)]
32pub enum ToolState {
33    BasicButton(ButtonState, bool),
34    BasicCheckbox(CheckboxState),
35    BasicChoice(ChoiceState<usize>),
36}
37
38/// A toolbar with Buttons, Checkboxes and Choics.
39///
40/// As an extra it can collapse a number of buttons into a Choice
41/// if there is not enough space available.
42#[derive(Debug)]
43pub struct Toolbar<'a> {
44    tools: Vec<Tool<'a>>,
45    style: Style,
46    block: Option<Block<'a>>,
47    spacing: u16,
48    key_style: Option<Style>,
49    button_style: ButtonStyle,
50    checkbox_style: CheckboxStyle,
51    choice_style: ChoiceStyle,
52    collapsed_style: ChoiceStyle,
53}
54
55/// Widget for the toolbar.
56#[derive(Debug)]
57pub struct ToolbarWidget<'a> {
58    tools: Vec<ToolWidget1<'a>>,
59    style: Style,
60    block: Option<Block<'a>>,
61}
62
63/// Widget for any popups.
64#[derive(Debug)]
65pub struct ToolbarPopup<'a> {
66    tools: Vec<ToolWidget2<'a>>,
67}
68
69/// Comprehensive styles.
70#[derive(Debug, Clone)]
71pub struct ToolbarStyle {
72    pub style: Style,
73    pub block: Option<Block<'static>>,
74    pub border_style: Option<Style>,
75    pub title_style: Option<Style>,
76
77    pub key_style: Option<Style>,
78
79    pub button: Option<ButtonStyle>,
80    pub checkbox: Option<CheckboxStyle>,
81    pub choice: Option<ChoiceStyle>,
82    pub collapsed: Option<ChoiceStyle>,
83
84    pub non_exhaustive: NonExhaustive,
85}
86
87/// State
88#[derive(Debug)]
89pub struct ToolbarState {
90    /// Full area.
91    /// __read only__
92    pub area: Rect,
93    /// Area inside the block.
94    /// __read only__
95    pub inner: Rect,
96
97    /// State for the collapsed buttons.
98    pub collapsed: ChoiceState<Option<usize>>,
99    /// Collapsed buttons are active?
100    pub collapsed_active: bool,
101    /// Other tool-state.
102    pub tools: Vec<Option<ToolState>>,
103    /// Choices can get the focus. This is to flip back to the
104    /// focus before.
105    pub focus_before: Option<FocusFlag>,
106
107    /// Container focus flag.
108    pub container: FocusFlag,
109
110    pub non_exhaustive: NonExhaustive,
111}
112
113impl Default for ToolbarStyle {
114    fn default() -> Self {
115        Self {
116            style: Default::default(),
117            block: Default::default(),
118            border_style: Default::default(),
119            title_style: Default::default(),
120            key_style: Default::default(),
121            button: Default::default(),
122            checkbox: Default::default(),
123            choice: Default::default(),
124            collapsed: Default::default(),
125            non_exhaustive: NonExhaustive,
126        }
127    }
128}
129
130impl<'a> Default for Toolbar<'a> {
131    fn default() -> Self {
132        Self {
133            tools: Default::default(),
134            style: Default::default(),
135            block: Default::default(),
136            spacing: 1,
137            key_style: Default::default(),
138            button_style: Default::default(),
139            checkbox_style: Default::default(),
140            choice_style: Default::default(),
141            collapsed_style: Default::default(),
142        }
143    }
144}
145
146impl<'a> Toolbar<'a> {
147    pub fn new() -> Self {
148        Self::default()
149    }
150
151    /// Spacing between tools.
152    pub fn spacing(mut self, sp: u16) -> Self {
153        self.spacing = sp;
154        self
155    }
156
157    /// Add a placeholder where the collapsed buttons will be rendered.
158    pub fn collapsed_buttons(mut self, text: impl Into<Cow<'a, str>>) -> Self {
159        self.tools.push(Tool::CollapsedButtons(text.into()));
160        self
161    }
162
163    /// Add a button.
164    /// * key - Hotkey text
165    /// * text - Button text
166    /// * collapsible - yes or no
167    pub fn button(
168        mut self,
169        key: impl Into<Cow<'a, str>>,
170        text: impl Into<Cow<'a, str>>,
171        collapsible: bool,
172    ) -> Self {
173        self.tools
174            .push(Tool::BasicButton(key.into(), text.into(), collapsible));
175        self
176    }
177
178    /// Add a checkbox.
179    /// * key - hotkey
180    /// * text - text
181    /// * checked
182    pub fn checkbox(
183        mut self,
184        key: impl Into<Cow<'a, str>>,
185        text: impl Into<Cow<'a, str>>,
186        checked: bool,
187    ) -> Self {
188        self.tools
189            .push(Tool::BasicCheckbox(key.into(), text.into(), checked));
190        self
191    }
192
193    /// Add a choice.
194    pub fn choice<V: Into<Line<'a>>>(
195        mut self,
196        key: impl Into<Cow<'a, str>>,
197        items: impl IntoIterator<Item = V>,
198        selected: usize,
199    ) -> Self {
200        self.tools.push(Tool::BasicChoice(
201            key.into(),
202            items.into_iter().map(|v| v.into()).collect(),
203            selected,
204        ));
205        self
206    }
207
208    /// Add some text to the toolbar.
209    pub fn text(mut self, text: impl Into<Line<'a>>) -> Self {
210        self.tools.push(Tool::Text(text.into()));
211        self
212    }
213
214    /// Set combined styles.
215    pub fn styles(mut self, styles: ToolbarStyle) -> Self {
216        self.style = styles.style;
217        if styles.block.is_some() {
218            self.block = styles.block;
219        }
220        if let Some(border_style) = styles.border_style {
221            self.block = self.block.map(|v| v.border_style(border_style));
222        }
223        if let Some(title_style) = styles.title_style {
224            self.block = self.block.map(|v| v.title_style(title_style));
225        }
226        self.block = self.block.map(|v| v.style(self.style));
227
228        if styles.key_style.is_some() {
229            self.key_style = styles.key_style;
230        }
231        if let Some(button) = styles.button {
232            self.button_style = button;
233        }
234        if let Some(checkbox) = styles.checkbox {
235            self.checkbox_style = checkbox;
236        }
237        if let Some(choice) = styles.choice {
238            self.choice_style = choice;
239        }
240        if let Some(collapsed) = styles.collapsed {
241            self.collapsed_style = collapsed;
242        }
243        self
244    }
245
246    /// Base style.
247    pub fn style(mut self, style: Style) -> Self {
248        self.style = style;
249        self.block = self.block.map(|v| v.style(self.style));
250        self
251    }
252
253    /// Block for the main widget.
254    pub fn block(mut self, block: Block<'a>) -> Self {
255        self.block = Some(block);
256        self.block = self.block.map(|v| v.style(self.style));
257        self
258    }
259
260    /// Keyboard short-cut style.
261    pub fn key_style(mut self, style: Style) -> Self {
262        self.key_style = Some(style);
263        self
264    }
265
266    /// Button style.
267    pub fn button_style(mut self, style: ButtonStyle) -> Self {
268        self.button_style = style;
269        self
270    }
271
272    /// Checkbox style.
273    pub fn checkbox_style(mut self, style: CheckboxStyle) -> Self {
274        self.checkbox_style = style;
275        self
276    }
277
278    /// Choice style.
279    pub fn choice_style(mut self, style: ChoiceStyle) -> Self {
280        self.choice_style = style;
281        self
282    }
283
284    /// Collapsed style.
285    pub fn collapsed_style(mut self, style: ChoiceStyle) -> Self {
286        self.collapsed_style = style;
287        self
288    }
289
290    // todo: width, height
291
292    /// Create the final widgets for rendering.
293    pub fn into_widgets(
294        self,
295        area: Rect,
296        state: &mut ToolbarState,
297    ) -> (ToolbarWidget<'a>, ToolbarPopup<'a>) {
298        let block = self.block.clone();
299        let style = self.style.clone();
300
301        let (t1, t2) = layout(self, area, state);
302        (
303            ToolbarWidget {
304                tools: t1,
305                style,
306                block,
307            },
308            ToolbarPopup { tools: t2 },
309        )
310    }
311}
312
313enum ToolLayout<'a> {
314    CollapsedButton(Line<'a>),
315    BasicButton(u16, Button<'a>, Line<'a>, bool),
316    BasicCheckbox(Checkbox<'a>),
317    BasicChoice(Line<'a>, Choice<'a, usize>, usize),
318    Text(Line<'a>),
319}
320
321enum ToolLayout2<'a> {
322    CollapsedPlaceHolder(),
323    CollapsedButton(Choice<'a, Option<usize>>),
324    BasicButton(usize, Button<'a>),
325    BasicCheckbox(usize, Checkbox<'a>),
326    BasicChoice(usize, Line<'a>, Choice<'a, usize>, usize),
327    Text(Line<'a>),
328}
329
330#[derive(Debug)]
331enum ToolWidget1<'a> {
332    CollapsedButton(Rect, ChoiceWidget<'a, Option<usize>>),
333    BasicButton(usize, Rect, Button<'a>),
334    BasicCheckbox(usize, Rect, Checkbox<'a>),
335    BasicChoice(
336        usize,
337        Rect,
338        Paired<'a, PairedWidget<'a, Line<'a>>, ChoiceWidget<'a, usize>>,
339    ),
340    Text(Rect, Line<'a>),
341}
342
343#[derive(Debug)]
344enum ToolWidget2<'a> {
345    CollapsedButton(ChoicePopup<'a, Option<usize>>),
346    BasicChoice(usize, ChoicePopup<'a, usize>),
347}
348
349fn layout<'a>(
350    widget: Toolbar<'a>,
351    area: Rect,
352    state: &mut ToolbarState,
353) -> (Vec<ToolWidget1<'a>>, Vec<ToolWidget2<'a>>) {
354    let inner = widget.block.inner_if_some(area);
355
356    let key_style = widget.key_style.unwrap_or_default();
357
358    // set up uncollapsed widgets
359    let mut layout1 = Vec::with_capacity(widget.tools.len());
360    let mut total_width = 0;
361    let mut have_collapsed = false;
362    for tool in widget.tools {
363        match tool {
364            Tool::CollapsedButtons(text) => {
365                let text = Line::from(text);
366                have_collapsed = true;
367                layout1.push(ToolLayout::CollapsedButton(text));
368            }
369            Tool::BasicButton(key, text, collapse) => {
370                let text =
371                    Line::from_iter([Span::from(key).style(key_style.clone()), Span::from(text)]);
372                let w = Button::new(text.clone()).styles(widget.button_style.clone());
373                let w_width = w.width() + widget.spacing;
374                total_width += w_width;
375                layout1.push(ToolLayout::BasicButton(w_width, w, text, collapse));
376            }
377            Tool::BasicCheckbox(key, text, checked) => {
378                let text =
379                    Line::from_iter([Span::from(key).style(key_style.clone()), Span::from(text)]);
380                let c = Checkbox::new()
381                    .text(text)
382                    .checked(checked)
383                    .styles(widget.checkbox_style.clone());
384                let w_width = c.width() + widget.spacing;
385                total_width += w_width;
386                layout1.push(ToolLayout::BasicCheckbox(c));
387            }
388            Tool::BasicChoice(key, items, selected) => {
389                let key = Line::from(key).style(key_style.clone());
390                let c = Choice::new()
391                    .items(items.into_iter().enumerate())
392                    .styles(widget.choice_style.clone());
393                let w_width = key.width() as u16 + c.width() + widget.spacing;
394                total_width += w_width;
395                layout1.push(ToolLayout::BasicChoice(key, c, selected));
396            }
397            Tool::Text(txt) => {
398                let w_width = txt.width() as u16 + widget.spacing;
399                total_width += w_width;
400                layout1.push(ToolLayout::Text(txt.clone()));
401            }
402        }
403    }
404
405    // reset collapsed active flag
406    state.collapsed_active = false;
407    for w in state.tools.iter_mut().flatten() {
408        if let ToolState::BasicButton(_, active) = w {
409            *active = false;
410        }
411    }
412
413    // collapse buttons if necessary
414    let mut layout2 = Vec::with_capacity(layout1.len());
415    if total_width > inner.width && have_collapsed {
416        let mut collapsed_width = 0;
417        let mut collapsed = Choice::<Option<usize>>::new()
418            .styles(widget.collapsed_style.clone())
419            .behave_select(ChoiceSelect::MouseMove);
420
421        let mut n = 0;
422        for w in layout1.into_iter() {
423            match w {
424                ToolLayout::CollapsedButton(text) => {
425                    collapsed = collapsed.unknown_item(text);
426                    layout2.push(ToolLayout2::CollapsedPlaceHolder());
427                }
428                ToolLayout::BasicButton(w, button, text, collapse) => {
429                    if total_width > inner.width && collapse {
430                        total_width -= w;
431                        total_width -= collapsed_width;
432
433                        collapsed = collapsed.item(Some(n), text);
434
435                        collapsed_width = collapsed.width() + widget.spacing;
436                        total_width += collapsed_width;
437                    } else {
438                        layout2.push(ToolLayout2::BasicButton(n, button));
439                    }
440                    n += 1;
441                }
442                ToolLayout::BasicCheckbox(c) => {
443                    layout2.push(ToolLayout2::BasicCheckbox(n, c));
444                    n += 1;
445                }
446                ToolLayout::BasicChoice(t, c, selected) => {
447                    layout2.push(ToolLayout2::BasicChoice(n, t, c, selected));
448                    n += 1;
449                }
450                ToolLayout::Text(t) => {
451                    layout2.push(ToolLayout2::Text(t));
452                }
453            }
454        }
455
456        for i in 0..layout2.len() {
457            if matches!(layout2[i], ToolLayout2::CollapsedPlaceHolder()) {
458                layout2[i] = ToolLayout2::CollapsedButton(collapsed);
459                break;
460            }
461        }
462    } else {
463        let mut n = 0;
464        for w in layout1.into_iter() {
465            match w {
466                ToolLayout::CollapsedButton(_) => {
467                    layout2.push(ToolLayout2::CollapsedPlaceHolder());
468                }
469                ToolLayout::BasicButton(_, b, _, _) => {
470                    layout2.push(ToolLayout2::BasicButton(n, b));
471                    n += 1;
472                }
473                ToolLayout::BasicCheckbox(c) => {
474                    layout2.push(ToolLayout2::BasicCheckbox(n, c));
475                    n += 1;
476                }
477                ToolLayout::BasicChoice(t, c, selected) => {
478                    layout2.push(ToolLayout2::BasicChoice(n, t, c, selected));
479                    n += 1;
480                }
481                ToolLayout::Text(t) => {
482                    layout2.push(ToolLayout2::Text(t));
483                }
484            }
485        }
486    }
487
488    // create effective widgets
489    let mut widgets1 = Vec::with_capacity(layout2.len());
490    let mut widgets2 = Vec::with_capacity(layout2.len());
491    let mut widget_area = inner;
492    for w in layout2 {
493        match w {
494            ToolLayout2::CollapsedPlaceHolder() => {
495                widget_area.width = 0;
496            }
497            ToolLayout2::CollapsedButton(w) => {
498                state.collapsed_active = true;
499
500                widget_area.width = w.width();
501                let (w, p) = w.into_widgets();
502                widgets1.push(ToolWidget1::CollapsedButton(widget_area, w));
503                widgets2.push(ToolWidget2::CollapsedButton(p));
504            }
505            ToolLayout2::BasicButton(n, w) => {
506                while state.tools.len() <= n {
507                    state.tools.push(None);
508                }
509                if state.tools[n].is_none() {
510                    state.tools[n] = Some(ToolState::BasicButton(ButtonState::default(), true));
511                } else {
512                    if let Some(ToolState::BasicButton(_, active)) = &mut state.tools[n] {
513                        *active = true;
514                    }
515                }
516
517                widget_area.width = w.width();
518                widgets1.push(ToolWidget1::BasicButton(n, widget_area, w));
519            }
520            ToolLayout2::BasicCheckbox(n, w) => {
521                while state.tools.len() <= n {
522                    state.tools.push(None);
523                }
524                if state.tools[n].is_none() {
525                    state.tools[n] = Some(ToolState::BasicCheckbox(CheckboxState::default()));
526                }
527                widget_area.width = w.width();
528                widgets1.push(ToolWidget1::BasicCheckbox(n, widget_area, w));
529            }
530            ToolLayout2::BasicChoice(n, key, w, selected) => {
531                while state.tools.len() <= n {
532                    state.tools.push(None);
533                }
534                if state.tools[n].is_none() {
535                    state.tools[n] = Some(ToolState::BasicChoice(ChoiceState::default()));
536                }
537                let Some(ToolState::BasicChoice(s)) = &mut state.tools[n] else {
538                    unreachable!("invalid_state");
539                };
540                if !s.is_popup_active() {
541                    s.set_value(selected);
542                }
543
544                let key_len = key.width() as u16;
545                widget_area.width = key_len + w.width();
546                let (w, p) = w.into_widgets();
547                widgets1.push(ToolWidget1::BasicChoice(
548                    n,
549                    widget_area,
550                    Paired::new(PairedWidget::new(key), w)
551                        .spacing(0)
552                        .split(PairSplit::Fix1(key_len)),
553                ));
554                widgets2.push(ToolWidget2::BasicChoice(n, p));
555            }
556            ToolLayout2::Text(w) => {
557                widget_area.width = w.width() as u16;
558                widgets1.push(ToolWidget1::Text(widget_area, w));
559            }
560        }
561
562        if widget_area.width > 0 {
563            widget_area.x += widget_area.width;
564            widget_area.x += widget.spacing;
565        }
566    }
567
568    // hide all.
569    state.collapsed.relocate_hidden();
570    state.collapsed.relocate_popup_hidden();
571    for w in state.tools.iter_mut().flatten() {
572        match w {
573            ToolState::BasicButton(w, _) => {
574                w.relocate_hidden();
575            }
576            ToolState::BasicCheckbox(w) => {
577                w.relocate_hidden();
578            }
579            ToolState::BasicChoice(w) => {
580                w.relocate_hidden();
581                w.relocate_popup_hidden();
582            }
583        }
584    }
585
586    (widgets1, widgets2)
587}
588
589impl<'a> StatefulWidget for ToolbarWidget<'a> {
590    type State = ToolbarState;
591
592    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
593        render_ref(self, area, buf, state);
594    }
595}
596
597fn render_ref(widget: ToolbarWidget, area: Rect, buf: &mut Buffer, state: &mut ToolbarState) {
598    state.area = area;
599    state.inner = widget.block.inner_if_some(area);
600
601    if widget.block.is_some() {
602        widget.block.render(area, buf);
603    } else {
604        buf.set_style(area, widget.style);
605    }
606
607    for w in widget.tools {
608        match w {
609            ToolWidget1::CollapsedButton(widget_area, w) => {
610                w.render(widget_area, buf, &mut state.collapsed);
611            }
612            ToolWidget1::BasicButton(n, widget_area, w) => {
613                let ToolState::BasicButton(state, _) = state.tools[n].as_mut().expect("state")
614                else {
615                    unreachable!("invalid_state");
616                };
617                w.render(widget_area, buf, state);
618            }
619            ToolWidget1::BasicCheckbox(n, widget_area, w) => {
620                let ToolState::BasicCheckbox(state) = state.tools[n].as_mut().expect("state")
621                else {
622                    unreachable!("invalid_state");
623                };
624                w.render(widget_area, buf, state);
625            }
626            ToolWidget1::BasicChoice(n, widget_area, w) => {
627                let ToolState::BasicChoice(state) = state.tools[n].as_mut().expect("state") else {
628                    unreachable!("invalid_state");
629                };
630                w.render(widget_area, buf, &mut PairedState::new(&mut (), state));
631            }
632            ToolWidget1::Text(widget_area, w) => {
633                w.render(widget_area, buf);
634            }
635        }
636    }
637}
638
639impl<'a> StatefulWidget for ToolbarPopup<'a> {
640    type State = ToolbarState;
641
642    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
643        render_popup(self, area, buf, state);
644    }
645}
646
647fn render_popup(widget: ToolbarPopup, _area: Rect, buf: &mut Buffer, state: &mut ToolbarState) {
648    for w in widget.tools {
649        match w {
650            ToolWidget2::CollapsedButton(w) => {
651                w.render(Rect::default(), buf, &mut state.collapsed);
652            }
653            ToolWidget2::BasicChoice(n, w) => {
654                let ToolState::BasicChoice(state) = state.tools[n].as_mut().expect("state") else {
655                    unreachable!("invalid_state");
656                };
657                w.render(Rect::default(), buf, state);
658            }
659        }
660    }
661}
662
663impl Default for ToolbarState {
664    fn default() -> Self {
665        Self {
666            area: Default::default(),
667            inner: Default::default(),
668            collapsed: Default::default(),
669            collapsed_active: Default::default(),
670            tools: Default::default(),
671            focus_before: Default::default(),
672            container: Default::default(),
673            non_exhaustive: NonExhaustive,
674        }
675    }
676}
677
678impl HasFocus for ToolbarState {
679    fn build(&self, builder: &mut FocusBuilder) {
680        for w in self.tools.iter().flatten() {
681            match w {
682                ToolState::BasicButton(_, _) => {}
683                ToolState::BasicCheckbox(_) => {}
684                ToolState::BasicChoice(w) => {
685                    builder.widget_navigate(w, Navigation::Leave);
686                }
687            }
688        }
689    }
690
691    fn focus(&self) -> FocusFlag {
692        self.container.clone()
693    }
694
695    fn area(&self) -> Rect {
696        self.area
697    }
698}
699
700impl RelocatableState for ToolbarState {
701    fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {}
702
703    fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
704        self.area.relocate(shift, clip);
705        self.inner.relocate(shift, clip);
706        self.collapsed.relocate(shift, clip);
707        self.collapsed.relocate_popup(shift, clip);
708        for w in self.tools.iter_mut().flatten() {
709            match w {
710                ToolState::BasicButton(w, active) => {
711                    if *active {
712                        w.relocate(shift, clip);
713                    }
714                }
715                ToolState::BasicCheckbox(w) => {
716                    w.relocate(shift, clip);
717                }
718                ToolState::BasicChoice(w) => {
719                    w.relocate_popup(shift, clip);
720                }
721            }
722        }
723    }
724}
725
726impl ToolbarState {
727    pub fn new() -> Self {
728        Self::default()
729    }
730}
731
732/// Result type for event-handling.
733pub enum ToolbarOutcome {
734    /// The given event was not handled at all.
735    Continue,
736    /// The event was handled, no repaint necessary.
737    Unchanged,
738    /// The event was handled, repaint necessary.
739    Changed,
740    /// Button N has been activated.
741    Pressed(usize),
742    /// Checkbox N has been checked/unchecked.
743    Checked(usize, bool),
744    /// Choice N has changed selection.
745    /// (N, Selection)
746    Selected(usize, usize),
747}
748
749impl ConsumedEvent for ToolbarOutcome {
750    fn is_consumed(&self) -> bool {
751        !matches!(self, ToolbarOutcome::Continue)
752    }
753}
754
755impl From<Outcome> for ToolbarOutcome {
756    fn from(value: Outcome) -> Self {
757        match value {
758            Outcome::Continue => ToolbarOutcome::Continue,
759            Outcome::Unchanged => ToolbarOutcome::Unchanged,
760            Outcome::Changed => ToolbarOutcome::Changed,
761        }
762    }
763}
764
765impl From<ToolbarOutcome> for Outcome {
766    fn from(value: ToolbarOutcome) -> Self {
767        match value {
768            ToolbarOutcome::Continue => Outcome::Continue,
769            ToolbarOutcome::Unchanged => Outcome::Unchanged,
770            ToolbarOutcome::Changed => Outcome::Changed,
771            ToolbarOutcome::Pressed(_) => Outcome::Changed,
772            ToolbarOutcome::Selected(_, _) => Outcome::Changed,
773            ToolbarOutcome::Checked(_, _) => Outcome::Changed,
774        }
775    }
776}
777
778/// Event-handling uses this as parameters.
779///
780/// A Focus instance is needed to switch Focus between the toolbar
781/// and other widgets.
782///
783/// KeyEvents are in order of the tools.
784///
785pub struct ToolbarKeys<'a, const N: usize> {
786    /// Focus
787    pub focus: &'a Focus,
788    /// Keys.
789    pub keys: [Option<KeyEvent>; N],
790}
791
792impl<const N: usize> HandleEvent<Event, ToolbarKeys<'_, N>, ToolbarOutcome> for ToolbarState {
793    fn handle(&mut self, event: &Event, qualifier: ToolbarKeys<N>) -> ToolbarOutcome {
794        if let Event::Key(event) = event {
795            for (n, key) in qualifier.keys.iter().enumerate() {
796                if let Some(key) = key.as_ref()
797                    && event.code == key.code
798                    && event.modifiers == key.modifiers
799                {
800                    match &mut self.tools[n] {
801                        Some(ToolState::BasicButton(w, _)) => event_flow!(
802                            return {
803                                match w.pressed(event.kind == KeyEventKind::Press) {
804                                    ButtonOutcome::Pressed => ToolbarOutcome::Pressed(n),
805                                    r => ToolbarOutcome::from(Outcome::from(r)),
806                                }
807                            }
808                        ),
809                        Some(ToolState::BasicCheckbox(w)) => event_flow!(
810                            return {
811                                if event.kind == KeyEventKind::Press {
812                                    w.flip_checked();
813                                    ToolbarOutcome::Checked(n, w.value())
814                                } else {
815                                    ToolbarOutcome::Unchanged
816                                }
817                            }
818                        ),
819                        Some(ToolState::BasicChoice(w)) => event_flow!(
820                            return {
821                                if event.kind == KeyEventKind::Press {
822                                    if w.is_focused() {
823                                        if let Some(focus_before) = self.focus_before.as_ref() {
824                                            qualifier.focus.focus(focus_before);
825                                        } else {
826                                            qualifier.focus.next();
827                                        }
828                                    } else {
829                                        qualifier.focus.focus(w);
830                                        self.focus_before = qualifier.focus.lost_focus();
831                                    }
832                                    ToolbarOutcome::Changed
833                                } else {
834                                    ToolbarOutcome::Unchanged
835                                }
836                            }
837                        ),
838                        None => {}
839                    }
840                }
841            }
842        }
843
844        if self.collapsed_active {
845            match self.collapsed.handle(event, Popup) {
846                ChoiceOutcome::Value | ChoiceOutcome::Changed => event_flow!(
847                    return {
848                        if !self.collapsed.is_popup_active() {
849                            if let Some(value) = self.collapsed.value() {
850                                self.collapsed.set_value(None);
851                                ToolbarOutcome::Pressed(value)
852                            } else {
853                                ToolbarOutcome::Changed
854                            }
855                        } else {
856                            ToolbarOutcome::Changed
857                        }
858                    }
859                ),
860                ChoiceOutcome::Continue => {
861                    if self.collapsed.lost_focus() {
862                        self.collapsed.set_value(None);
863                    }
864                }
865                r => event_flow!(return ToolbarOutcome::from(Outcome::from(r))),
866            }
867        }
868
869        for (n, w) in self.tools.iter_mut().enumerate() {
870            match w {
871                Some(ToolState::BasicButton(w, active)) => event_flow!(
872                    return {
873                        if !*active {
874                            continue;
875                        }
876                        match w.handle(event, Regular) {
877                            ButtonOutcome::Pressed => ToolbarOutcome::Pressed(n),
878                            r => ToolbarOutcome::from(Outcome::from(r)),
879                        }
880                    }
881                ),
882                Some(ToolState::BasicCheckbox(w)) => event_flow!(
883                    return match w.handle(event, Regular) {
884                        CheckOutcome::Value => ToolbarOutcome::Checked(n, w.value()),
885                        r => ToolbarOutcome::from(Outcome::from(r)),
886                    }
887                ),
888                Some(ToolState::BasicChoice(w)) => event_flow!(
889                    return match w.handle(event, Popup) {
890                        ChoiceOutcome::Value | ChoiceOutcome::Changed => {
891                            if !w.is_popup_active() {
892                                if let Some(focus_before) = self.focus_before.as_ref() {
893                                    qualifier.focus.focus(focus_before);
894                                } else {
895                                    qualifier.focus.next();
896                                }
897                                ToolbarOutcome::Selected(n, w.value())
898                            } else {
899                                ToolbarOutcome::Changed
900                            }
901                        }
902                        r => ToolbarOutcome::from(Outcome::from(r)),
903                    }
904                ),
905                None => {}
906            }
907        }
908
909        ToolbarOutcome::Continue
910    }
911}