Skip to main content

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