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:
Eduardo Flores 2022-10-13 02:23:37 -07:00 committed by Michael Murphy
parent 420d3c3dfc
commit 07e53ddadd
8 changed files with 390 additions and 383 deletions

View file

@ -1,4 +1,4 @@
use cosmic::widget::{expander, ListBox}; use cosmic::widget::{expander, nav_bar, nav_bar_page, nav_bar_section};
use cosmic::{ use cosmic::{
iced::widget::{ iced::widget::{
checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider, 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::{self, theme, Alignment, Application, Color, Command, Element, Length, Theme},
iced_lazy::responsive, iced_lazy::responsive,
iced_winit::window::{drag, maximize, minimize}, iced_winit::window::{drag, maximize, minimize},
list_item, list_row, list_section, list_view, nav_button, scrollable, list_view, list_view_item, list_view_row, list_view_section, scrollable,
widget::{button, header_bar, list_row, list_view::*, nav_bar::nav_bar_style, toggler}, widget::{button, header_bar, list_box, list_row, list_view::*, toggler},
}; };
use std::collections::BTreeMap;
#[derive(Default)] #[derive(Default)]
pub struct Window { pub struct Window {
@ -101,7 +102,7 @@ impl Application for Window {
Message::RowSelected(row) => println!("Selected row {row}"), Message::RowSelected(row) => println!("Selected row {row}"),
} }
iced::Command::none() Command::none()
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
@ -128,38 +129,84 @@ impl Application for Window {
let content = responsive(|size| { let content = responsive(|size| {
let condensed = size.width < 900.0; let condensed = size.width < 900.0;
let sidebar: Element<_> = cosmic::navbar![ // cosmic::navbar![
nav_button!("network-wireless", "Wi-Fi", condensed) // nav_button!("network-wireless", "Network & Wireless", condensed)
.on_press(Message::Page(0)) // .on_press(Message::Page(0))
.style(if self.page == 0 { // .style(if self.page == 0 {
theme::Button::Primary // theme::Button::Primary
} else { // } else {
theme::Button::Text // theme::Button::Text
}), // }),
nav_button!("preferences-desktop", "Desktop", condensed) // nav_button!("preferences-desktop", "Bluetooth", condensed)
.on_press(Message::Page(1)) // .on_press(Message::Page(1))
.style(if self.page == 1 { // .style(if self.page == 1 {
theme::Button::Primary // theme::Button::Primary
} else { // } else {
theme::Button::Text // theme::Button::Text
}), // }),
nav_button!("system-software-update", "OS Upgrade & Recovery", condensed) // nav_button!("system-software-update", "Personalization", condensed)
.on_press(Message::Page(2)) // .on_press(Message::Page(2))
.style(if self.page == 2 { // .style(if self.page == 2 {
theme::Button::Primary // theme::Button::Primary
} else { // } else {
theme::Button::Text // theme::Button::Text
}), // }),
] // ]
.active(self.sidebar_toggled)
.condensed(condensed) let sidebar: Element<_> = nav_bar()
.style(theme::Container::Custom(nav_bar_style)) .source(BTreeMap::from([
.into(); (
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( let choose_theme = [Theme::Light, Theme::Dark].iter().fold(
row![text("Debug theme:")] row![].spacing(10).align_items(Alignment::Center),
.spacing(10)
.align_items(Alignment::Center),
|row, theme| { |row, theme| {
row.push(radio( row.push(radio(
format!("{:?}", theme), format!("{:?}", theme),
@ -171,14 +218,17 @@ impl Application for Window {
); );
let content: Element<_> = list_view!( let content: Element<_> = list_view!(
list_section!( list_view_section!(
"Debug", "Debug",
choose_theme, list_view_item!("Debug theme", choose_theme),
toggler(String::from("Debug layout"), self.debug, Message::Debug,) list_view_item!(
"Debug layout",
toggler(String::from("Debug layout"), self.debug, Message::Debug,)
)
), ),
list_section!( list_view_section!(
"Buttons", "Buttons",
list_row!( list_view_row!(
button!("Primary") button!("Primary")
.style(theme::Button::Primary) .style(theme::Button::Primary)
.on_press(Message::ButtonPressed), .on_press(Message::ButtonPressed),
@ -195,7 +245,7 @@ impl Application for Window {
.style(theme::Button::Text) .style(theme::Button::Text)
.on_press(Message::ButtonPressed), .on_press(Message::ButtonPressed),
), ),
list_row!( list_view_row!(
button!("Primary").style(theme::Button::Primary), button!("Primary").style(theme::Button::Primary),
button!("Secondary").style(theme::Button::Secondary), button!("Secondary").style(theme::Button::Secondary),
button!("Positive").style(theme::Button::Positive), button!("Positive").style(theme::Button::Positive),
@ -203,13 +253,13 @@ impl Application for Window {
button!("Text").style(theme::Button::Text), button!("Text").style(theme::Button::Text),
), ),
), ),
list_section!( list_view_section!(
"Controls", "Controls",
list_item!( list_view_item!(
"Toggler", "Toggler",
toggler(None, self.toggler_value, Message::TogglerToggled) toggler(None, self.toggler_value, Message::TogglerToggled)
), ),
list_item!( list_view_item!(
"Pick List (TODO)", "Pick List (TODO)",
pick_list( pick_list(
vec!["Option 1", "Option 2", "Option 3", "Option 4",], vec!["Option 1", "Option 2", "Option 3", "Option 4",],
@ -218,12 +268,12 @@ impl Application for Window {
) )
.padding([8, 0, 8, 16]) .padding([8, 0, 8, 16])
), ),
list_item!( list_view_item!(
"Slider", "Slider",
slider(0.0..=100.0, self.slider_value, Message::SliderChanged) slider(0.0..=100.0, self.slider_value, Message::SliderChanged)
.width(Length::Units(250)) .width(Length::Units(250))
), ),
list_item!( list_view_item!(
"Progress", "Progress",
progress_bar(0.0..=100.0, self.slider_value) progress_bar(0.0..=100.0, self.slider_value)
.width(Length::Units(250)) .width(Length::Units(250))
@ -231,7 +281,7 @@ impl Application for Window {
), ),
checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled), checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled),
), ),
list_section!( list_view_section!(
"Expander", "Expander",
expander() expander()
.title("Label") .title("Label")
@ -247,19 +297,18 @@ impl Application for Window {
list_row().title("Label") list_row().title("Label")
]) ])
), ),
list_section!( list_view_section!(
"List Box", "List Box",
ListBox::with_children( list_box()
vec![ .style(theme::Container::Custom(list_section_style))
.children(vec![
cosmic::list_box_row!("Title").into(), cosmic::list_box_row!("Title").into(),
cosmic::list_box_row!("Title", "Subtitle").into(), cosmic::list_box_row!("Title", "Subtitle").into(),
cosmic::list_box_row!("Title", "", "edit-paste").into(), cosmic::list_box_row!("Title", "", "edit-paste").into(),
cosmic::list_box_row!("", "Subtitle", "edit-paste").into(), cosmic::list_box_row!("", "Subtitle", "edit-paste").into(),
cosmic::list_box_row!("Title", "Subtitle", "edit-paste").into() cosmic::list_box_row!("Title", "Subtitle", "edit-paste").into()
], ])
true, .render()
)
.style(theme::Container::Custom(list_section_style))
), ),
) )
.into(); .into();

View file

@ -1,6 +1,6 @@
use std::vec; use std::vec;
use crate::{list_box_row, widget::ListRow}; use crate::{list_box_row, separator, widget::ListRow};
use apply::Apply; use apply::Apply;
use derive_setters::Setters; use derive_setters::Setters;
use iced::{ use iced::{
@ -9,7 +9,7 @@ use iced::{
Alignment, Background, Element, Length, Renderer, Theme, Alignment, Background, Element, Length, Renderer, Theme,
}; };
use iced_lazy::Component; use iced_lazy::Component;
use iced_native::widget::{column, event_container, horizontal_rule}; use iced_native::widget::{column, event_container};
#[derive(Setters)] #[derive(Setters)]
pub struct Expander<'a, Message> { pub struct Expander<'a, Message> {
@ -145,12 +145,7 @@ impl<'a, Message: Clone + 'a> Component<Message, Renderer> for Expander<'a, Mess
.enumerate() .enumerate()
.flat_map(|(index, child)| { .flat_map(|(index, child)| {
if index != rows.len() - 1 { if index != rows.len() - 1 {
vec![ vec![child, separator!(1).into()]
child,
horizontal_rule(1)
.style(theme::Rule::Custom(separator_style))
.into(),
]
} else { } else {
vec![child] vec![child]
} }

View file

@ -1,3 +1,4 @@
use crate::separator;
use derive_setters::Setters; use derive_setters::Setters;
use iced::mouse::Interaction; use iced::mouse::Interaction;
use iced::{overlay, Alignment, Length, Padding, Point, Rectangle}; 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::layout::{Limits, Node};
use iced_native::overlay::from_children; use iced_native::overlay::from_children;
use iced_native::renderer::Style; use iced_native::renderer::Style;
use iced_native::widget::{column, horizontal_rule, Operation, Tree}; use iced_native::widget::{column, Operation, Tree};
use iced_native::{ use iced_native::{
renderer, row, Background, Clipboard, Color, Element, Event, Layout, Shell, Widget, renderer, row, Background, Clipboard, Color, Element, Event, Layout, Shell, Widget,
}; };
@ -15,6 +16,7 @@ use iced_style::theme;
use iced_style::theme::Container; use iced_style::theme::Container;
#[derive(Setters)] #[derive(Setters)]
#[allow(dead_code)]
pub struct ListBox<'a, Message, Renderer> pub struct ListBox<'a, Message, Renderer>
where where
Renderer: iced_native::Renderer, Renderer: iced_native::Renderer,
@ -22,6 +24,7 @@ where
<Renderer as iced_native::Renderer>::Theme: iced_style::rule::StyleSheet, <Renderer as iced_native::Renderer>::Theme: iced_style::rule::StyleSheet,
{ {
spacing: u16, spacing: u16,
#[setters(into)]
padding: Padding, padding: Padding,
width: Length, width: Length,
height: Length, height: Length,
@ -31,6 +34,7 @@ where
children: Vec<Element<'a, Message, Renderer>>, children: Vec<Element<'a, Message, Renderer>>,
#[setters(strip_option)] #[setters(strip_option)]
placeholder: Option<Element<'a, Message, Renderer>>, placeholder: Option<Element<'a, Message, Renderer>>,
show_separators: bool,
on_item_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>, on_item_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>,
} }
@ -57,51 +61,54 @@ where
/// Creates an empty [`ListBox`]. /// Creates an empty [`ListBox`].
pub fn new() -> Self { 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`]. /// Creates a new [`ListBox`].
/// ///
/// [`ListBox`]: struct.ListBox.html /// [`ListBox`]: struct.ListBox.html
pub fn with_children( pub fn with_children(children: Vec<Element<'a, Message, Renderer>>) -> Self {
children: Vec<Element<'a, Message, Renderer>>, let list_box = Self {
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 {
spacing: 0, spacing: 0,
padding: Padding::from(Self::DEFAULT_PADDING), padding: Padding::from(Self::DEFAULT_PADDING),
width: Length::Shrink, width: Length::Shrink,
height: Length::Shrink, height: Length::Shrink,
max_width: u32::MAX, max_width: u32::MAX,
align_items: Alignment::Start, align_items: Alignment::Center,
style: Default::default(), style: Default::default(),
children, children,
placeholder: None, placeholder: None,
show_separators: true,
on_item_selected: None, 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`]. /// Adds an element to the [`ListBox`].
pub fn push(mut self, child: impl Into<Element<'a, Message, Renderer>>) -> Self { pub fn push(mut self, child: impl Into<Element<'a, Message, Renderer>>) -> Self {
self.children.push(child.into()); self.children.push(child.into());
self = self.render();
self self
} }
} }
@ -444,5 +451,3 @@ macro_rules! list_box_heading {
}; };
} }
pub use list_box_heading; pub use list_box_heading;
use crate::widget::separator_style;

View file

@ -14,7 +14,7 @@ pub mod list_view {
} }
#[macro_export] #[macro_export]
macro_rules! list_row { macro_rules! list_view_row {
($($x:expr),+ $(,)?) => ( ($($x:expr),+ $(,)?) => (
$crate::iced::widget::Row::with_children(vec![ $crate::iced::widget::Row::with_children(vec![
$($crate::iced::Element::from($x)),+ $($crate::iced::Element::from($x)),+
@ -26,7 +26,7 @@ pub mod list_view {
} }
#[macro_export] #[macro_export]
macro_rules! list_section { macro_rules! list_view_section {
($title:expr, $($x:expr),+ $(,)?) => ( ($title:expr, $($x:expr),+ $(,)?) => (
$crate::iced::widget::Column::with_children(vec![ $crate::iced::widget::Column::with_children(vec![
$crate::iced::widget::Text::new($title) $crate::iced::widget::Text::new($title)
@ -39,7 +39,7 @@ pub mod list_view {
//TODO: more efficient method for adding separators //TODO: more efficient method for adding separators
let mut i = 1; let mut i = 1;
while i < children.len() { while i < children.len() {
children.insert(i, $crate::iced::widget::horizontal_rule(12).into()); children.insert(i, $crate::separator!(12).into());
i += 2; i += 2;
} }
@ -57,9 +57,9 @@ pub mod list_view {
} }
#[macro_export] #[macro_export]
macro_rules! list_item { macro_rules! list_view_item {
($title:expr, $($x:expr),+ $(,)?) => ( ($title:expr, $($x:expr),+ $(,)?) => (
$crate::list_row!( $crate::list_view_row!(
$crate::iced::widget::Text::new($title), $crate::iced::widget::Text::new($title),
$crate::iced::widget::horizontal_space( $crate::iced::widget::horizontal_space(
$crate::iced::Length::Fill $crate::iced::Length::Fill
@ -84,10 +84,10 @@ pub mod list_view {
use iced::widget; use iced::widget;
use iced_style::Theme; use iced_style::Theme;
pub use list_item;
pub use list_row;
pub use list_section;
pub use list_view; pub use list_view;
pub use list_view_item;
pub use list_view_row;
pub use list_view_section;
} }
pub mod list_box { pub mod list_box {

View file

@ -21,3 +21,6 @@ pub use expander::*;
pub mod list; pub mod list;
pub use list::*; pub use list::*;
pub mod separator;
pub use separator::*;

View file

@ -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; let cosmic = &theme.cosmic().primary;
widget::container::Appearance { widget::container::Appearance {
text_color: Some(cosmic.on.into()), 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; pub use nav_button;
} }

View file

@ -1,313 +1,249 @@
use iced::{ use crate::scrollable;
alignment, use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style};
widget::{scrollable, Column, Container}, use crate::widget::{icon, Background};
Alignment, Element, Length, Padding, use derive_setters::Setters;
}; use iced::Length;
use iced_native::{ use iced_lazy::Component;
renderer, row, use iced_native::widget::{button, column, container, text};
widget::{ use iced_native::{row, Alignment, Element};
container::{draw_background, layout}, use iced_style::button::Appearance;
Tree, use iced_style::{scrollable, theme, Theme};
}, use std::collections::BTreeMap;
Widget,
};
use iced_style::container::StyleSheet;
pub struct NavBar<'a, Message, Renderer> #[derive(Setters, Default)]
where pub struct NavBar<'a, Message> {
Renderer: iced_native::Renderer, source: BTreeMap<NavBarSection, Vec<NavBarPage>>,
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,
active: bool, 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> impl<'a, Message> NavBar<'a, Message> {
where pub fn new() -> Self {
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 {
Self { Self {
spacing: 12, source: Default::default(),
padding: Padding::new(12), active: false,
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(),
condensed: false, condensed: false,
active: true, on_page_selected: None,
content: Container::new(row![Column::new()]).into(),
} }
} }
} }
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 where
Renderer: iced_native::Renderer, Renderer: iced_native::Renderer + iced_native::text::Renderer + iced_native::svg::Renderer + 'a,
Renderer::Theme: StyleSheet, <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 { type State = NavBarState;
self.width type Event = NavBarEvent;
}
fn height(&self) -> Length { fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
self.height match event {
} NavBarEvent::SectionSelected(section) => {
if state.selected_section == section {
fn layout( state.section_active = !state.section_active;
&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)
} else { } else {
let content: Element<Message, Renderer> = state.selected_section = section;
Container::new(row![Column::new()]).into(); state.section_active = true;
content.as_widget().layout(renderer, limits)
} }
}, state.selected_page = None;
) state.page_active = false;
} None
}
fn draw( NavBarEvent::PageSelected(section, page) => {
&self, if state.selected_page.is_some() && &page == state.selected_page.as_ref().unwrap() {
tree: &Tree, state.page_active = !state.page_active;
renderer: &mut Renderer, } else {
theme: &Renderer::Theme, state.selected_page = Some(page.clone());
renderer_style: &iced_native::renderer::Style, state.page_active = true;
layout: iced_native::Layout<'_>, }
cursor_position: iced::Point, self.on_page_selected
viewport: &iced::Rectangle, .as_ref()
) { .map(|on_page_selected| (on_page_selected)(section, page))
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,
);
} }
} }
fn children(&self) -> Vec<iced_native::widget::Tree> { fn view(&self, state: &Self::State) -> Element<'a, Self::Event, Renderer> {
vec![Tree::new(&self.content)] 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) { for (section, section_pages) in &self.source {
tree.diff_children(std::slice::from_ref(&self.content)) sections.push(
} button(
column(vec![
icon(&section.icon, 20).into(),
text(&section.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( let nav_bar: Element<Self::Event, Renderer> =
&self, container(if self.condensed && state.selected_page.is_some() {
tree: &mut Tree, row![container(scrollable!(column(pages)
layout: iced_native::Layout<'_>, .spacing(10)
operation: &mut dyn iced_native::widget::Operation<Message>, .padding(10)
) { .max_width(200)
operation.container(None, &mut |operation| { .width(Length::Units(200))
self.content.as_widget().operate( .height(Length::Shrink)))
&mut tree.children[0], .height(Length::Fill)
layout.children().next().unwrap(), .style(theme::Container::Custom(nav_bar_pages_style))]
operation, } else if !state.section_active || self.condensed && state.selected_page.is_none() {
); row![scrollable!(column(sections)
}); .spacing(10)
} .padding(10)
.max_width(100)
fn on_event( .align_items(Alignment::Center)
&mut self, .height(Length::Shrink))]
tree: &mut iced_native::widget::Tree, } else {
event: iced::Event, row![
layout: iced_native::Layout<'_>, scrollable!(column(sections)
cursor_position: iced::Point, .spacing(10)
renderer: &Renderer, .padding(10)
clipboard: &mut dyn iced_native::Clipboard, .max_width(100)
shell: &mut iced_native::Shell<'_, Message>, .align_items(Alignment::Center)
) -> iced::event::Status { .height(Length::Shrink)),
self.content.as_widget_mut().on_event( container(scrollable!(column(pages)
&mut tree.children[0], .spacing(10)
event, .padding(10)
layout.children().next().unwrap(), .max_width(200)
cursor_position, .width(Length::Units(200))
renderer, .height(Length::Shrink)))
clipboard, .height(Length::Fill)
shell, .style(theme::Container::Custom(nav_bar_pages_style)),
) ]
} })
.height(Length::Fill)
fn mouse_interaction( .style(theme::Container::Custom(nav_bar_sections_style))
&self, .into();
tree: &iced_native::widget::Tree, nav_bar
layout: iced_native::Layout<'_>, } else {
cursor_position: iced::Point, row![].into()
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,
)
} }
} }
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 where
Message: 'a, Renderer: iced_native::text::Renderer + iced_native::svg::Renderer + 'a,
Renderer: iced_native::Renderer + 'a, <Renderer as iced_native::Renderer>::Theme:
Renderer::Theme: StyleSheet, 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 { fn from(nav_bar: NavBar<'a, Message>) -> Self {
Self::new(navbar) iced_lazy::component(nav_bar)
} }
} }
/// Creates a [NavBar`] with the given children. pub fn section_button_style(theme: &Theme) -> Appearance {
/// let primary = &theme.cosmic().primary;
/// [`NavBar`]: widget::NavBar Appearance {
#[macro_export] shadow_offset: Default::default(),
macro_rules! navbar { background: Some(Background::Color(primary.base.into())),
($($x:expr),+ $(,)?) => ( border_radius: 5.0,
$crate::widget::NavBar::with_children(vec![$($crate::iced::Element::from($x)),+]) border_width: 0.0,
); border_color: Default::default(),
text_color: Default::default(),
}
} }

7
src/widget/separator.rs Normal file
View 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))
};
}