Reimplemented NavigationBar
- Navigation Bar was reimplemented to support sections and pages. - Created new widget called separator, a horizontal rule with the COSMIC theme.
This commit is contained in:
parent
420d3c3dfc
commit
07e53ddadd
8 changed files with 390 additions and 383 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use cosmic::widget::{expander, ListBox};
|
||||
use cosmic::widget::{expander, nav_bar, nav_bar_page, nav_bar_section};
|
||||
use cosmic::{
|
||||
iced::widget::{
|
||||
checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
||||
|
|
@ -7,9 +7,10 @@ use cosmic::{
|
|||
iced::{self, theme, Alignment, Application, Color, Command, Element, Length, Theme},
|
||||
iced_lazy::responsive,
|
||||
iced_winit::window::{drag, maximize, minimize},
|
||||
list_item, list_row, list_section, list_view, nav_button, scrollable,
|
||||
widget::{button, header_bar, list_row, list_view::*, nav_bar::nav_bar_style, toggler},
|
||||
list_view, list_view_item, list_view_row, list_view_section, scrollable,
|
||||
widget::{button, header_bar, list_box, list_row, list_view::*, toggler},
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Window {
|
||||
|
|
@ -101,7 +102,7 @@ impl Application for Window {
|
|||
Message::RowSelected(row) => println!("Selected row {row}"),
|
||||
}
|
||||
|
||||
iced::Command::none()
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
|
|
@ -128,38 +129,84 @@ impl Application for Window {
|
|||
let content = responsive(|size| {
|
||||
let condensed = size.width < 900.0;
|
||||
|
||||
let sidebar: Element<_> = cosmic::navbar![
|
||||
nav_button!("network-wireless", "Wi-Fi", condensed)
|
||||
.on_press(Message::Page(0))
|
||||
.style(if self.page == 0 {
|
||||
theme::Button::Primary
|
||||
} else {
|
||||
theme::Button::Text
|
||||
}),
|
||||
nav_button!("preferences-desktop", "Desktop", condensed)
|
||||
.on_press(Message::Page(1))
|
||||
.style(if self.page == 1 {
|
||||
theme::Button::Primary
|
||||
} else {
|
||||
theme::Button::Text
|
||||
}),
|
||||
nav_button!("system-software-update", "OS Upgrade & Recovery", condensed)
|
||||
.on_press(Message::Page(2))
|
||||
.style(if self.page == 2 {
|
||||
theme::Button::Primary
|
||||
} else {
|
||||
theme::Button::Text
|
||||
}),
|
||||
]
|
||||
.active(self.sidebar_toggled)
|
||||
.condensed(condensed)
|
||||
.style(theme::Container::Custom(nav_bar_style))
|
||||
.into();
|
||||
// cosmic::navbar![
|
||||
// nav_button!("network-wireless", "Network & Wireless", condensed)
|
||||
// .on_press(Message::Page(0))
|
||||
// .style(if self.page == 0 {
|
||||
// theme::Button::Primary
|
||||
// } else {
|
||||
// theme::Button::Text
|
||||
// }),
|
||||
// nav_button!("preferences-desktop", "Bluetooth", condensed)
|
||||
// .on_press(Message::Page(1))
|
||||
// .style(if self.page == 1 {
|
||||
// theme::Button::Primary
|
||||
// } else {
|
||||
// theme::Button::Text
|
||||
// }),
|
||||
// nav_button!("system-software-update", "Personalization", condensed)
|
||||
// .on_press(Message::Page(2))
|
||||
// .style(if self.page == 2 {
|
||||
// theme::Button::Primary
|
||||
// } else {
|
||||
// theme::Button::Text
|
||||
// }),
|
||||
// ]
|
||||
|
||||
let sidebar: Element<_> = nav_bar()
|
||||
.source(BTreeMap::from([
|
||||
(
|
||||
nav_bar_section()
|
||||
.title("Network & Wireless")
|
||||
.icon("network-wireless"),
|
||||
vec![nav_bar_page("Wi-Fi")],
|
||||
),
|
||||
(
|
||||
nav_bar_section()
|
||||
.title("Bluetooth")
|
||||
.icon("cs-bluetooth"),
|
||||
vec![nav_bar_page("Devices")],
|
||||
),
|
||||
(
|
||||
nav_bar_section()
|
||||
.title("Personalization")
|
||||
.icon("applications-system"),
|
||||
vec![
|
||||
nav_bar_page("Desktop Session"),
|
||||
nav_bar_page("Wallpaper"),
|
||||
nav_bar_page("Appearance"),
|
||||
nav_bar_page("Dock & Top Panel"),
|
||||
nav_bar_page("Workspaces"),
|
||||
nav_bar_page("Notifications"),
|
||||
],
|
||||
),
|
||||
(
|
||||
nav_bar_section()
|
||||
.title("Input Devices")
|
||||
.icon("input-keyboard"),
|
||||
vec![nav_bar_page("Keyboard")],
|
||||
),
|
||||
(
|
||||
nav_bar_section().title("Displays").icon("cs-display"),
|
||||
vec![nav_bar_page("Keyboard")],
|
||||
),
|
||||
(
|
||||
nav_bar_section()
|
||||
.title("Power & Battery")
|
||||
.icon("battery"),
|
||||
vec![nav_bar_page("Status")],
|
||||
),
|
||||
(
|
||||
nav_bar_section().title("Sound").icon("sound"),
|
||||
vec![nav_bar_page("Volume")],
|
||||
),
|
||||
]))
|
||||
.active(self.sidebar_toggled)
|
||||
.condensed(condensed)
|
||||
.into();
|
||||
|
||||
let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
|
||||
row![text("Debug theme:")]
|
||||
.spacing(10)
|
||||
.align_items(Alignment::Center),
|
||||
row![].spacing(10).align_items(Alignment::Center),
|
||||
|row, theme| {
|
||||
row.push(radio(
|
||||
format!("{:?}", theme),
|
||||
|
|
@ -171,14 +218,17 @@ impl Application for Window {
|
|||
);
|
||||
|
||||
let content: Element<_> = list_view!(
|
||||
list_section!(
|
||||
list_view_section!(
|
||||
"Debug",
|
||||
choose_theme,
|
||||
toggler(String::from("Debug layout"), self.debug, Message::Debug,)
|
||||
list_view_item!("Debug theme", choose_theme),
|
||||
list_view_item!(
|
||||
"Debug layout",
|
||||
toggler(String::from("Debug layout"), self.debug, Message::Debug,)
|
||||
)
|
||||
),
|
||||
list_section!(
|
||||
list_view_section!(
|
||||
"Buttons",
|
||||
list_row!(
|
||||
list_view_row!(
|
||||
button!("Primary")
|
||||
.style(theme::Button::Primary)
|
||||
.on_press(Message::ButtonPressed),
|
||||
|
|
@ -195,7 +245,7 @@ impl Application for Window {
|
|||
.style(theme::Button::Text)
|
||||
.on_press(Message::ButtonPressed),
|
||||
),
|
||||
list_row!(
|
||||
list_view_row!(
|
||||
button!("Primary").style(theme::Button::Primary),
|
||||
button!("Secondary").style(theme::Button::Secondary),
|
||||
button!("Positive").style(theme::Button::Positive),
|
||||
|
|
@ -203,13 +253,13 @@ impl Application for Window {
|
|||
button!("Text").style(theme::Button::Text),
|
||||
),
|
||||
),
|
||||
list_section!(
|
||||
list_view_section!(
|
||||
"Controls",
|
||||
list_item!(
|
||||
list_view_item!(
|
||||
"Toggler",
|
||||
toggler(None, self.toggler_value, Message::TogglerToggled)
|
||||
),
|
||||
list_item!(
|
||||
list_view_item!(
|
||||
"Pick List (TODO)",
|
||||
pick_list(
|
||||
vec!["Option 1", "Option 2", "Option 3", "Option 4",],
|
||||
|
|
@ -218,12 +268,12 @@ impl Application for Window {
|
|||
)
|
||||
.padding([8, 0, 8, 16])
|
||||
),
|
||||
list_item!(
|
||||
list_view_item!(
|
||||
"Slider",
|
||||
slider(0.0..=100.0, self.slider_value, Message::SliderChanged)
|
||||
.width(Length::Units(250))
|
||||
),
|
||||
list_item!(
|
||||
list_view_item!(
|
||||
"Progress",
|
||||
progress_bar(0.0..=100.0, self.slider_value)
|
||||
.width(Length::Units(250))
|
||||
|
|
@ -231,7 +281,7 @@ impl Application for Window {
|
|||
),
|
||||
checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled),
|
||||
),
|
||||
list_section!(
|
||||
list_view_section!(
|
||||
"Expander",
|
||||
expander()
|
||||
.title("Label")
|
||||
|
|
@ -247,19 +297,18 @@ impl Application for Window {
|
|||
list_row().title("Label")
|
||||
])
|
||||
),
|
||||
list_section!(
|
||||
list_view_section!(
|
||||
"List Box",
|
||||
ListBox::with_children(
|
||||
vec![
|
||||
list_box()
|
||||
.style(theme::Container::Custom(list_section_style))
|
||||
.children(vec![
|
||||
cosmic::list_box_row!("Title").into(),
|
||||
cosmic::list_box_row!("Title", "Subtitle").into(),
|
||||
cosmic::list_box_row!("Title", "", "edit-paste").into(),
|
||||
cosmic::list_box_row!("", "Subtitle", "edit-paste").into(),
|
||||
cosmic::list_box_row!("Title", "Subtitle", "edit-paste").into()
|
||||
],
|
||||
true,
|
||||
)
|
||||
.style(theme::Container::Custom(list_section_style))
|
||||
])
|
||||
.render()
|
||||
),
|
||||
)
|
||||
.into();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::vec;
|
||||
|
||||
use crate::{list_box_row, widget::ListRow};
|
||||
use crate::{list_box_row, separator, widget::ListRow};
|
||||
use apply::Apply;
|
||||
use derive_setters::Setters;
|
||||
use iced::{
|
||||
|
|
@ -9,7 +9,7 @@ use iced::{
|
|||
Alignment, Background, Element, Length, Renderer, Theme,
|
||||
};
|
||||
use iced_lazy::Component;
|
||||
use iced_native::widget::{column, event_container, horizontal_rule};
|
||||
use iced_native::widget::{column, event_container};
|
||||
|
||||
#[derive(Setters)]
|
||||
pub struct Expander<'a, Message> {
|
||||
|
|
@ -145,12 +145,7 @@ impl<'a, Message: Clone + 'a> Component<Message, Renderer> for Expander<'a, Mess
|
|||
.enumerate()
|
||||
.flat_map(|(index, child)| {
|
||||
if index != rows.len() - 1 {
|
||||
vec![
|
||||
child,
|
||||
horizontal_rule(1)
|
||||
.style(theme::Rule::Custom(separator_style))
|
||||
.into(),
|
||||
]
|
||||
vec![child, separator!(1).into()]
|
||||
} else {
|
||||
vec![child]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::separator;
|
||||
use derive_setters::Setters;
|
||||
use iced::mouse::Interaction;
|
||||
use iced::{overlay, Alignment, Length, Padding, Point, Rectangle};
|
||||
|
|
@ -6,7 +7,7 @@ use iced_native::layout::flex::{resolve, Axis};
|
|||
use iced_native::layout::{Limits, Node};
|
||||
use iced_native::overlay::from_children;
|
||||
use iced_native::renderer::Style;
|
||||
use iced_native::widget::{column, horizontal_rule, Operation, Tree};
|
||||
use iced_native::widget::{column, Operation, Tree};
|
||||
use iced_native::{
|
||||
renderer, row, Background, Clipboard, Color, Element, Event, Layout, Shell, Widget,
|
||||
};
|
||||
|
|
@ -15,6 +16,7 @@ use iced_style::theme;
|
|||
use iced_style::theme::Container;
|
||||
|
||||
#[derive(Setters)]
|
||||
#[allow(dead_code)]
|
||||
pub struct ListBox<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
|
|
@ -22,6 +24,7 @@ where
|
|||
<Renderer as iced_native::Renderer>::Theme: iced_style::rule::StyleSheet,
|
||||
{
|
||||
spacing: u16,
|
||||
#[setters(into)]
|
||||
padding: Padding,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -31,6 +34,7 @@ where
|
|||
children: Vec<Element<'a, Message, Renderer>>,
|
||||
#[setters(strip_option)]
|
||||
placeholder: Option<Element<'a, Message, Renderer>>,
|
||||
show_separators: bool,
|
||||
on_item_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>,
|
||||
}
|
||||
|
||||
|
|
@ -57,51 +61,54 @@ where
|
|||
|
||||
/// Creates an empty [`ListBox`].
|
||||
pub fn new() -> Self {
|
||||
Self::with_children(Vec::<Element<Message, Renderer>>::new(), true)
|
||||
Self::with_children(Vec::<Element<Message, Renderer>>::new()).render()
|
||||
}
|
||||
|
||||
/// Creates a new [`ListBox`].
|
||||
///
|
||||
/// [`ListBox`]: struct.ListBox.html
|
||||
pub fn with_children(
|
||||
children: Vec<Element<'a, Message, Renderer>>,
|
||||
show_separators: bool,
|
||||
) -> Self {
|
||||
let children_size = children.len();
|
||||
let children: Vec<Element<Message, Renderer>> = children
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, child)| {
|
||||
let row_items = if show_separators && index != children_size - 1 {
|
||||
vec![
|
||||
row![child].align_items(Alignment::Center).into(),
|
||||
horizontal_rule(1)
|
||||
.style(theme::Rule::Custom(separator_style))
|
||||
.into(),
|
||||
]
|
||||
} else {
|
||||
vec![row![child].align_items(Alignment::Center).into()]
|
||||
};
|
||||
column(row_items).into()
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
pub fn with_children(children: Vec<Element<'a, Message, Renderer>>) -> Self {
|
||||
let list_box = Self {
|
||||
spacing: 0,
|
||||
padding: Padding::from(Self::DEFAULT_PADDING),
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
align_items: Alignment::Start,
|
||||
align_items: Alignment::Center,
|
||||
style: Default::default(),
|
||||
children,
|
||||
placeholder: None,
|
||||
show_separators: true,
|
||||
on_item_selected: None,
|
||||
}
|
||||
};
|
||||
list_box.render()
|
||||
}
|
||||
|
||||
pub fn render(mut self) -> Self {
|
||||
let children_size = self.children.len();
|
||||
self.children = self
|
||||
.children
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, child)| {
|
||||
let row_items = if self.show_separators && index != children_size - 1 {
|
||||
vec![
|
||||
row![child].align_items(self.align_items).into(),
|
||||
separator!(1).into(),
|
||||
]
|
||||
} else {
|
||||
vec![row![child].align_items(self.align_items).into()]
|
||||
};
|
||||
column(row_items).spacing(self.spacing).into()
|
||||
})
|
||||
.collect();
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds an element to the [`ListBox`].
|
||||
pub fn push(mut self, child: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
self.children.push(child.into());
|
||||
self = self.render();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -444,5 +451,3 @@ macro_rules! list_box_heading {
|
|||
};
|
||||
}
|
||||
pub use list_box_heading;
|
||||
|
||||
use crate::widget::separator_style;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ pub mod list_view {
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! list_row {
|
||||
macro_rules! list_view_row {
|
||||
($($x:expr),+ $(,)?) => (
|
||||
$crate::iced::widget::Row::with_children(vec![
|
||||
$($crate::iced::Element::from($x)),+
|
||||
|
|
@ -26,7 +26,7 @@ pub mod list_view {
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! list_section {
|
||||
macro_rules! list_view_section {
|
||||
($title:expr, $($x:expr),+ $(,)?) => (
|
||||
$crate::iced::widget::Column::with_children(vec![
|
||||
$crate::iced::widget::Text::new($title)
|
||||
|
|
@ -39,7 +39,7 @@ pub mod list_view {
|
|||
//TODO: more efficient method for adding separators
|
||||
let mut i = 1;
|
||||
while i < children.len() {
|
||||
children.insert(i, $crate::iced::widget::horizontal_rule(12).into());
|
||||
children.insert(i, $crate::separator!(12).into());
|
||||
i += 2;
|
||||
}
|
||||
|
||||
|
|
@ -57,9 +57,9 @@ pub mod list_view {
|
|||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! list_item {
|
||||
macro_rules! list_view_item {
|
||||
($title:expr, $($x:expr),+ $(,)?) => (
|
||||
$crate::list_row!(
|
||||
$crate::list_view_row!(
|
||||
$crate::iced::widget::Text::new($title),
|
||||
$crate::iced::widget::horizontal_space(
|
||||
$crate::iced::Length::Fill
|
||||
|
|
@ -84,10 +84,10 @@ pub mod list_view {
|
|||
use iced::widget;
|
||||
use iced_style::Theme;
|
||||
|
||||
pub use list_item;
|
||||
pub use list_row;
|
||||
pub use list_section;
|
||||
pub use list_view;
|
||||
pub use list_view_item;
|
||||
pub use list_view_row;
|
||||
pub use list_view_section;
|
||||
}
|
||||
|
||||
pub mod list_box {
|
||||
|
|
|
|||
|
|
@ -21,3 +21,6 @@ pub use expander::*;
|
|||
|
||||
pub mod list;
|
||||
pub use list::*;
|
||||
|
||||
pub mod separator;
|
||||
pub use separator::*;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ pub mod nav_bar {
|
|||
}};
|
||||
}
|
||||
|
||||
pub fn nav_bar_style(theme: &Theme) -> widget::container::Appearance {
|
||||
pub fn nav_bar_sections_style(theme: &Theme) -> widget::container::Appearance {
|
||||
let cosmic = &theme.cosmic().primary;
|
||||
widget::container::Appearance {
|
||||
text_color: Some(cosmic.on.into()),
|
||||
|
|
@ -27,5 +27,17 @@ pub mod nav_bar {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn nav_bar_pages_style(theme: &Theme) -> widget::container::Appearance {
|
||||
let primary = &theme.cosmic().primary;
|
||||
let secondary = &theme.cosmic().secondary;
|
||||
widget::container::Appearance {
|
||||
text_color: Some(primary.on.into()),
|
||||
background: Some(Background::Color(secondary.component.base.into())),
|
||||
border_radius: 8.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
}
|
||||
}
|
||||
|
||||
pub use nav_button;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,313 +1,249 @@
|
|||
use iced::{
|
||||
alignment,
|
||||
widget::{scrollable, Column, Container},
|
||||
Alignment, Element, Length, Padding,
|
||||
};
|
||||
use iced_native::{
|
||||
renderer, row,
|
||||
widget::{
|
||||
container::{draw_background, layout},
|
||||
Tree,
|
||||
},
|
||||
Widget,
|
||||
};
|
||||
use iced_style::container::StyleSheet;
|
||||
use crate::scrollable;
|
||||
use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style};
|
||||
use crate::widget::{icon, Background};
|
||||
use derive_setters::Setters;
|
||||
use iced::Length;
|
||||
use iced_lazy::Component;
|
||||
use iced_native::widget::{button, column, container, text};
|
||||
use iced_native::{row, Alignment, Element};
|
||||
use iced_style::button::Appearance;
|
||||
use iced_style::{scrollable, theme, Theme};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub struct NavBar<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
spacing: u16,
|
||||
padding: Padding,
|
||||
width: Length,
|
||||
height: Length,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
align_items: Alignment,
|
||||
horizontal_alignment: alignment::Horizontal,
|
||||
vertical_alignment: alignment::Vertical,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
condensed: bool,
|
||||
#[derive(Setters, Default)]
|
||||
pub struct NavBar<'a, Message> {
|
||||
source: BTreeMap<NavBarSection, Vec<NavBarPage>>,
|
||||
active: bool,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
condensed: bool,
|
||||
on_page_selected: Option<Box<dyn Fn(NavBarSection, NavBarPage) -> Message + 'a>>,
|
||||
}
|
||||
|
||||
impl<'a, Message: 'a, Renderer> NavBar<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
/// Creates a [`NavBar`] with the given elements.
|
||||
pub fn with_children(children: Vec<Element<'a, Message, Renderer>>) -> Self
|
||||
where
|
||||
<Renderer as iced_native::Renderer>::Theme: iced_style::scrollable::StyleSheet,
|
||||
{
|
||||
let nav = Self::default();
|
||||
NavBar {
|
||||
content: Container::new(
|
||||
scrollable(row![Column::with_children(children)
|
||||
.spacing(nav.spacing)
|
||||
.padding(nav.padding)])
|
||||
.scrollbar_width(6)
|
||||
.scroller_width(6),
|
||||
)
|
||||
.into(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn condensed(mut self, condensed: bool) -> Self {
|
||||
self.condensed = condensed;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn active(mut self, active: bool) -> Self {
|
||||
self.active = active;
|
||||
self
|
||||
}
|
||||
/// Sets the horizontal spacing _between_ elements.
|
||||
///
|
||||
/// Custom margins per element do not exist in iced. You should use this
|
||||
/// method instead! While less flexible, it helps you keep spacing between
|
||||
/// elements consistent.
|
||||
pub fn spacing(mut self, units: u16) -> Self {
|
||||
self.spacing = units;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Padding`] of the [`NavBar`].
|
||||
pub fn padding<P: Into<iced::Padding>>(mut self, padding: P) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`NavBar`].
|
||||
pub fn width(mut self, width: iced::Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`NavBar`].
|
||||
pub fn height(mut self, height: iced::Length) -> Self {
|
||||
self.height = height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the vertical alignment of the contents of the [`NavBar`] .
|
||||
pub fn align_items(mut self, align: iced::Alignment) -> Self {
|
||||
self.align_items = align;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`NavBar`].
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`NavBar`].
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`NavBar`].
|
||||
pub fn style(mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'a, Renderer> Default for NavBar<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn default() -> Self {
|
||||
impl<'a, Message> NavBar<'a, Message> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
spacing: 12,
|
||||
padding: Padding::new(12),
|
||||
width: Length::Shrink,
|
||||
height: Length::Fill,
|
||||
max_width: 300,
|
||||
max_height: u32::MAX,
|
||||
align_items: Alignment::Start,
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
style: Default::default(),
|
||||
source: Default::default(),
|
||||
active: false,
|
||||
condensed: false,
|
||||
active: true,
|
||||
content: Container::new(row![Column::new()]).into(),
|
||||
on_page_selected: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for NavBar<'a, Message, Renderer>
|
||||
pub fn nav_bar<'a, Message>() -> NavBar<'a, Message> {
|
||||
NavBar::new()
|
||||
}
|
||||
|
||||
#[derive(Setters, Clone, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
pub struct NavBarSection {
|
||||
#[setters(into)]
|
||||
title: String,
|
||||
#[setters(into)]
|
||||
icon: String,
|
||||
}
|
||||
|
||||
impl NavBarSection {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
title: String::new(),
|
||||
icon: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nav_bar_section() -> NavBarSection {
|
||||
NavBarSection::new()
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Setters, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct NavBarPage {
|
||||
#[setters(into)]
|
||||
title: String,
|
||||
}
|
||||
|
||||
impl NavBarPage {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
title: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nav_bar_page(title: &str) -> NavBarPage {
|
||||
let mut page = NavBarPage::new();
|
||||
page.title = title.to_string();
|
||||
page
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum NavBarEvent {
|
||||
SectionSelected(NavBarSection),
|
||||
PageSelected(NavBarSection, NavBarPage),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NavBarState {
|
||||
selected_section: NavBarSection,
|
||||
section_active: bool,
|
||||
selected_page: Option<NavBarPage>,
|
||||
page_active: bool,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Component<Message, Renderer> for NavBar<'a, Message>
|
||||
where
|
||||
Renderer: iced_native::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Renderer: iced_native::Renderer + iced_native::text::Renderer + iced_native::svg::Renderer + 'a,
|
||||
<Renderer as iced_native::Renderer>::Theme:
|
||||
container::StyleSheet + button::StyleSheet + text::StyleSheet + scrollable::StyleSheet,
|
||||
<<Renderer as iced_native::Renderer>::Theme as button::StyleSheet>::Style: From<theme::Button>,
|
||||
<<Renderer as iced_native::Renderer>::Theme as container::StyleSheet>::Style:
|
||||
From<theme::Container>,
|
||||
<<Renderer as iced_native::Renderer>::Theme as text::StyleSheet>::Style: From<theme::Text>,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
type State = NavBarState;
|
||||
type Event = NavBarEvent;
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &iced_native::layout::Limits,
|
||||
) -> iced_native::layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
if self.condensed { 100 } else { self.max_width },
|
||||
self.max_height,
|
||||
if self.active {
|
||||
self.padding
|
||||
} else {
|
||||
Padding::ZERO
|
||||
},
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
|renderer, limits| {
|
||||
if self.active {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
|
||||
match event {
|
||||
NavBarEvent::SectionSelected(section) => {
|
||||
if state.selected_section == section {
|
||||
state.section_active = !state.section_active;
|
||||
} else {
|
||||
let content: Element<Message, Renderer> =
|
||||
Container::new(row![Column::new()]).into();
|
||||
content.as_widget().layout(renderer, limits)
|
||||
state.selected_section = section;
|
||||
state.section_active = true;
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
renderer_style: &iced_native::renderer::Style,
|
||||
layout: iced_native::Layout<'_>,
|
||||
cursor_position: iced::Point,
|
||||
viewport: &iced::Rectangle,
|
||||
) {
|
||||
if self.active {
|
||||
let style = theme.appearance(self.style);
|
||||
|
||||
draw_background(renderer, &style, layout.bounds());
|
||||
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
text_color: style.text_color.unwrap_or(renderer_style.text_color),
|
||||
},
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
state.selected_page = None;
|
||||
state.page_active = false;
|
||||
None
|
||||
}
|
||||
NavBarEvent::PageSelected(section, page) => {
|
||||
if state.selected_page.is_some() && &page == state.selected_page.as_ref().unwrap() {
|
||||
state.page_active = !state.page_active;
|
||||
} else {
|
||||
state.selected_page = Some(page.clone());
|
||||
state.page_active = true;
|
||||
}
|
||||
self.on_page_selected
|
||||
.as_ref()
|
||||
.map(|on_page_selected| (on_page_selected)(section, page))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<iced_native::widget::Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
fn view(&self, state: &Self::State) -> Element<'a, Self::Event, Renderer> {
|
||||
if self.active {
|
||||
let mut sections: Vec<Element<Self::Event, Renderer>> = vec![];
|
||||
let mut pages: Vec<Element<Self::Event, Renderer>> = vec![];
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
}
|
||||
for (section, section_pages) in &self.source {
|
||||
sections.push(
|
||||
button(
|
||||
column(vec![
|
||||
icon(§ion.icon, 20).into(),
|
||||
text(§ion.title).size(14).into(),
|
||||
])
|
||||
.width(Length::Units(100))
|
||||
.height(Length::Units(50))
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.style(if *section == state.selected_section && state.section_active {
|
||||
theme::Button::Primary.into()
|
||||
} else {
|
||||
theme::Button::Text.into()
|
||||
})
|
||||
.on_press(NavBarEvent::SectionSelected(section.clone()))
|
||||
.into(),
|
||||
);
|
||||
if *section == state.selected_section {
|
||||
for page in section_pages {
|
||||
pages.push(
|
||||
button(row![text(&page.title).size(16).width(Length::Fill)])
|
||||
.padding(10)
|
||||
.style(
|
||||
if let Some(selected_page) = &state.selected_page {
|
||||
if state.page_active && page == selected_page {
|
||||
theme::Button::Primary.into()
|
||||
} else {
|
||||
theme::Button::Text.into()
|
||||
}
|
||||
} else {
|
||||
theme::Button::Text.into()
|
||||
}
|
||||
)
|
||||
.on_press(NavBarEvent::PageSelected(section.clone(), page.clone()))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: iced_native::Layout<'_>,
|
||||
operation: &mut dyn iced_native::widget::Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
operation,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut iced_native::widget::Tree,
|
||||
event: iced::Event,
|
||||
layout: iced_native::Layout<'_>,
|
||||
cursor_position: iced::Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn iced_native::Clipboard,
|
||||
shell: &mut iced_native::Shell<'_, Message>,
|
||||
) -> iced::event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &iced_native::widget::Tree,
|
||||
layout: iced_native::Layout<'_>,
|
||||
cursor_position: iced::Point,
|
||||
viewport: &iced::Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> iced_native::mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut iced_native::widget::Tree,
|
||||
layout: iced_native::Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<iced_native::overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
)
|
||||
let nav_bar: Element<Self::Event, Renderer> =
|
||||
container(if self.condensed && state.selected_page.is_some() {
|
||||
row![container(scrollable!(column(pages)
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
.max_width(200)
|
||||
.width(Length::Units(200))
|
||||
.height(Length::Shrink)))
|
||||
.height(Length::Fill)
|
||||
.style(theme::Container::Custom(nav_bar_pages_style))]
|
||||
} else if !state.section_active || self.condensed && state.selected_page.is_none() {
|
||||
row![scrollable!(column(sections)
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
.max_width(100)
|
||||
.align_items(Alignment::Center)
|
||||
.height(Length::Shrink))]
|
||||
} else {
|
||||
row![
|
||||
scrollable!(column(sections)
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
.max_width(100)
|
||||
.align_items(Alignment::Center)
|
||||
.height(Length::Shrink)),
|
||||
container(scrollable!(column(pages)
|
||||
.spacing(10)
|
||||
.padding(10)
|
||||
.max_width(200)
|
||||
.width(Length::Units(200))
|
||||
.height(Length::Shrink)))
|
||||
.height(Length::Fill)
|
||||
.style(theme::Container::Custom(nav_bar_pages_style)),
|
||||
]
|
||||
})
|
||||
.height(Length::Fill)
|
||||
.style(theme::Container::Custom(nav_bar_sections_style))
|
||||
.into();
|
||||
nav_bar
|
||||
} else {
|
||||
row![].into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<NavBar<'a, Message, Renderer>> for Element<'a, Message, Renderer>
|
||||
impl<'a, Message: 'a, Renderer> From<NavBar<'a, Message>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: iced_native::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Renderer: iced_native::text::Renderer + iced_native::svg::Renderer + 'a,
|
||||
<Renderer as iced_native::Renderer>::Theme:
|
||||
container::StyleSheet + button::StyleSheet + text::StyleSheet + scrollable::StyleSheet,
|
||||
<<Renderer as iced_native::Renderer>::Theme as button::StyleSheet>::Style: From<theme::Button>,
|
||||
<<Renderer as iced_native::Renderer>::Theme as container::StyleSheet>::Style:
|
||||
From<theme::Container>,
|
||||
<<Renderer as iced_native::Renderer>::Theme as text::StyleSheet>::Style: From<theme::Text>,
|
||||
{
|
||||
fn from(navbar: NavBar<'a, Message, Renderer>) -> Self {
|
||||
Self::new(navbar)
|
||||
fn from(nav_bar: NavBar<'a, Message>) -> Self {
|
||||
iced_lazy::component(nav_bar)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [NavBar`] with the given children.
|
||||
///
|
||||
/// [`NavBar`]: widget::NavBar
|
||||
#[macro_export]
|
||||
macro_rules! navbar {
|
||||
($($x:expr),+ $(,)?) => (
|
||||
$crate::widget::NavBar::with_children(vec![$($crate::iced::Element::from($x)),+])
|
||||
);
|
||||
pub fn section_button_style(theme: &Theme) -> Appearance {
|
||||
let primary = &theme.cosmic().primary;
|
||||
Appearance {
|
||||
shadow_offset: Default::default(),
|
||||
background: Some(Background::Color(primary.base.into())),
|
||||
border_radius: 5.0,
|
||||
border_width: 0.0,
|
||||
border_color: Default::default(),
|
||||
text_color: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
7
src/widget/separator.rs
Normal file
7
src/widget/separator.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#[macro_export]
|
||||
macro_rules! separator {
|
||||
($size:expr) => {
|
||||
$crate::iced::widget::horizontal_rule($size)
|
||||
.style(theme::Rule::Custom($crate::widget::separator_style))
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue