cosmic/widget/
nav_bar.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Navigation side panel for switching between views.
5//!
6//! For details on the model, see the [`segmented_button`] module for more details.
7
8use apply::Apply;
9use iced::{
10    Background, Length,
11    clipboard::{dnd::DndAction, mime::AllowedMimeTypes},
12};
13use iced_core::{Border, Color, Shadow};
14
15use crate::widget::{Container, Icon, container, menu, scrollable, segmented_button};
16use crate::{Theme, theme};
17
18use super::dnd_destination::DragId;
19
20pub type Id = segmented_button::Entity;
21pub type Model = segmented_button::SingleSelectModel;
22
23/// Navigation side panel for switching between views.
24///
25/// For details on the model, see the [`segmented_button`] module for more details.
26pub fn nav_bar<Message: Clone + 'static>(
27    model: &segmented_button::SingleSelectModel,
28    on_activate: fn(segmented_button::Entity) -> Message,
29) -> NavBar<Message> {
30    NavBar {
31        segmented_button: segmented_button::vertical(model).on_activate(on_activate),
32    }
33}
34
35/// Navigation side panel for switching between views.
36/// Can receive drag and drop events.
37pub fn nav_bar_dnd<Message, D: AllowedMimeTypes>(
38    model: &segmented_button::SingleSelectModel,
39    on_activate: fn(segmented_button::Entity) -> Message,
40    on_dnd_enter: impl Fn(segmented_button::Entity, Vec<String>) -> Message + 'static,
41    on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static,
42    on_dnd_drop: impl Fn(segmented_button::Entity, Option<D>, DndAction) -> Message + 'static,
43    id: DragId,
44) -> NavBar<Message>
45where
46    Message: Clone + 'static,
47{
48    NavBar {
49        segmented_button: segmented_button::vertical(model)
50            .on_activate(on_activate)
51            .on_dnd_enter(on_dnd_enter)
52            .on_dnd_leave(on_dnd_leave)
53            .on_dnd_drop(on_dnd_drop)
54            .drag_id(id),
55    }
56}
57
58#[must_use]
59pub struct NavBar<'a, Message> {
60    segmented_button:
61        segmented_button::VerticalSegmentedButton<'a, segmented_button::SingleSelect, Message>,
62}
63
64impl<'a, Message: Clone + 'static> NavBar<'a, Message> {
65    #[inline]
66    pub fn close_icon(mut self, close_icon: Icon) -> Self {
67        self.segmented_button = self.segmented_button.close_icon(close_icon);
68        self
69    }
70
71    #[inline]
72    pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self {
73        self.segmented_button = self.segmented_button.context_menu(context_menu);
74        self
75    }
76
77    #[inline]
78    pub fn drag_id(mut self, id: DragId) -> Self {
79        self.segmented_button = self.segmented_button.drag_id(id);
80        self
81    }
82
83    /// Pre-convert this widget into the [`Container`] widget that it becomes.
84    #[must_use]
85    #[inline]
86    pub fn into_container(self) -> Container<'a, Message, crate::Theme, crate::Renderer> {
87        Container::from(self)
88    }
89
90    /// Emitted when a tab close button is pressed.
91    pub fn on_close<T>(mut self, on_close: T) -> Self
92    where
93        T: Fn(Id) -> Message + 'static,
94    {
95        self.segmented_button = self.segmented_button.on_close(on_close);
96        self
97    }
98
99    /// Emitted when a button is right-clicked.
100    pub fn on_context<T>(mut self, on_context: T) -> Self
101    where
102        T: Fn(Id) -> Message + 'static,
103    {
104        self.segmented_button = self.segmented_button.on_context(on_context);
105        self
106    }
107
108    /// Emitted when the middle mouse button is pressed on a button.
109    pub fn on_middle_press<T>(mut self, on_middle_press: T) -> Self
110    where
111        T: Fn(Id) -> Message + 'static,
112    {
113        self.segmented_button = self.segmented_button.on_middle_press(on_middle_press);
114        self
115    }
116
117    /// Handle the dnd drop event.
118    pub fn on_dnd_drop<D: AllowedMimeTypes>(
119        mut self,
120        handler: impl Fn(Id, Option<D>, DndAction) -> Message + 'static,
121    ) -> Self {
122        self.segmented_button = self.segmented_button.on_dnd_drop(handler);
123        self
124    }
125
126    /// Handle the dnd enter event.
127    pub fn on_dnd_enter(mut self, handler: impl Fn(Id, Vec<String>) -> Message + 'static) -> Self {
128        self.segmented_button = self.segmented_button.on_dnd_enter(handler);
129        self
130    }
131
132    /// Handle the dnd leave event.
133    pub fn on_dnd_leave(mut self, handler: impl Fn(Id) -> Message + 'static) -> Self {
134        self.segmented_button = self.segmented_button.on_dnd_leave(handler);
135        self
136    }
137}
138
139impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>>
140    for Container<'a, Message, crate::Theme, crate::Renderer>
141{
142    fn from(this: NavBar<'a, Message>) -> Self {
143        let theme = crate::theme::active();
144        let space_s = theme.cosmic().space_s();
145        let space_xxs = theme.cosmic().space_xxs();
146
147        this.segmented_button
148            .button_height(32)
149            .button_padding([space_s, space_xxs, space_s, space_xxs])
150            .button_spacing(space_xxs)
151            .spacing(space_xxs)
152            .style(crate::theme::SegmentedButton::TabBar)
153            .apply(container)
154            .padding(space_xxs)
155            .apply(scrollable)
156            .class(crate::style::iced::Scrollable::Minimal)
157            .height(Length::Fill)
158            .apply(container)
159            .height(Length::Fill)
160            .class(theme::Container::custom(nav_bar_style))
161    }
162}
163
164impl<'a, Message: Clone + 'static> From<NavBar<'a, Message>> for crate::Element<'a, Message> {
165    fn from(this: NavBar<'a, Message>) -> Self {
166        Container::from(this).into()
167    }
168}
169
170#[must_use]
171pub fn nav_bar_style(theme: &Theme) -> iced_widget::container::Style {
172    let cosmic = &theme.cosmic();
173    iced_widget::container::Style {
174        icon_color: Some(cosmic.on_bg_color().into()),
175        text_color: Some(cosmic.on_bg_color().into()),
176        background: Some(Background::Color(cosmic.primary.base.into())),
177        border: Border {
178            width: 0.0,
179            color: Color::TRANSPARENT,
180            radius: cosmic.corner_radii.radius_s.into(),
181        },
182        shadow: Shadow::default(),
183    }
184}