From b1cbcfaf5b3081fbe57fc3f858a66c0b74130064 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 6 Jan 2023 01:41:54 +0100 Subject: [PATCH] refactor: Replace nav bar macros with nav bar widget --- examples/cosmic/src/window.rs | 147 ++++++++--------- examples/cosmic/src/window/demo.rs | 37 ++--- src/widget/nav_button.rs | 12 +- src/widget/navigation/macros.rs | 58 ------- src/widget/navigation/mod.rs | 8 - src/widget/navigation/navbar.rs | 245 ----------------------------- 6 files changed, 89 insertions(+), 418 deletions(-) delete mode 100644 src/widget/navigation/macros.rs delete mode 100644 src/widget/navigation/mod.rs delete mode 100644 src/widget/navigation/navbar.rs diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 27ad613..a7aff0c 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -11,7 +11,11 @@ use cosmic::{ iced_native::{subscription, window}, iced_winit::window::{close, drag, minimize, toggle_maximize}, theme::{self, Theme}, - widget::{header_bar, icon, list, nav_bar, nav_button, scrollable, settings}, + widget::{ + header_bar, icon, list, nav_bar, nav_button, scrollable, + segmented_button::{self, cosmic::vertical_view_switcher}, + settings, + }, Element, ElementExt, }; use once_cell::sync::Lazy; @@ -128,23 +132,24 @@ const BREAK_POINT: u32 = 900; #[derive(Default)] pub struct Window { - title: String, - page: Page, - debug: bool, - theme: Theme, bluetooth: bluetooth::State, + debug: bool, demo: demo::State, desktop: desktop::State, - system_and_accounts: system_and_accounts::State, - sidebar_toggled: bool, - sidebar_toggled_condensed: bool, - show_minimize: bool, + nav_bar_pages: segmented_button::State, + nav_bar_toggled_condensed: bool, + nav_bar_toggled: bool, + page: Page, show_maximize: bool, + show_minimize: bool, + system_and_accounts: system_and_accounts::State, + theme: Theme, + title: String, } impl Window { - pub fn sidebar_toggled(mut self, toggled: bool) -> Self { - self.sidebar_toggled = toggled; + pub fn nav_bar_toggled(mut self, toggled: bool) -> Self { + self.nav_bar_toggled = toggled; self } @@ -162,19 +167,20 @@ impl Window { #[allow(dead_code)] #[derive(Clone, Copy, Debug)] pub enum Message { + Bluetooth(bluetooth::Message), Close, CondensedViewToggle, - TabNav(bool), - Bluetooth(bluetooth::Message), Demo(demo::Message), Desktop(desktop::Message), Drag, InputChanged, Maximize, Minimize, + NavBar(segmented_button::Key), Page(Page), - ToggleSidebar, - ToggleSidebarCondensed, + TabNav(bool), + ToggleNavBar, + ToggleNavBarCondensed, } impl From for Message { @@ -193,7 +199,7 @@ impl Window { } fn page(&mut self, page: Page) { - self.sidebar_toggled_condensed = false; + self.nav_bar_toggled_condensed = false; self.page = page; } @@ -279,11 +285,36 @@ impl Application for Window { fn new(_flags: ()) -> (Self, Command) { let mut window = Window::default() - .sidebar_toggled(true) + .nav_bar_toggled(true) .show_maximize(true) .show_minimize(true); window.title = String::from("COSMIC Design System - Iced"); + + let mut add_page = |page: Page| { + let content = segmented_button::Content::default() + .text(page.title()) + .icon(page.icon_name()); + window.nav_bar_pages.insert(content, page) + }; + + add_page(Page::Demo); + add_page(Page::WiFi); + add_page(Page::Networking(None)); + add_page(Page::Bluetooth); + let key = add_page(Page::Desktop(None)); + add_page(Page::InputDevices(None)); + add_page(Page::Displays); + add_page(Page::PowerAndBattery); + add_page(Page::Sound); + add_page(Page::PrintersAndScanners); + add_page(Page::PrivacyAndSecurity); + add_page(Page::SystemAndAccounts(None)); + add_page(Page::TimeAndLanguage(None)); + add_page(Page::Accessibility); + add_page(Page::Applications); + window.nav_bar_pages.activate(key); + (window, Command::none()) } @@ -332,6 +363,12 @@ impl Application for Window { fn update(&mut self, message: Message) -> iced::Command { let mut ret = Command::none(); match message { + Message::NavBar(key) => { + if let Some(page) = self.nav_bar_pages.data(key).cloned() { + self.nav_bar_pages.activate(key); + self.page(page); + } + } Message::Page(page) => self.page(page), Message::Bluetooth(message) => { self.bluetooth.update(message); @@ -345,9 +382,9 @@ impl Application for Window { Some(desktop::Output::Page(page)) => self.page(page), None => (), }, - Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled, - Message::ToggleSidebarCondensed => { - self.sidebar_toggled_condensed = !self.sidebar_toggled_condensed + Message::ToggleNavBar => self.nav_bar_toggled = !self.nav_bar_toggled, + Message::ToggleNavBarCondensed => { + self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed } Message::Drag => return drag(window::Id::new(0)), Message::Close => return close(window::Id::new(0)), @@ -369,13 +406,13 @@ impl Application for Window { } fn view(&self) -> Element { - let (sidebar_message, sidebar_toggled) = if self.is_condensed() { + let (nav_bar_message, nav_bar_toggled) = if self.is_condensed() { ( - Message::ToggleSidebarCondensed, - self.sidebar_toggled_condensed, + Message::ToggleNavBarCondensed, + self.nav_bar_toggled_condensed, ) } else { - (Message::ToggleSidebar, self.sidebar_toggled) + (Message::ToggleNavBar, self.nav_bar_toggled) }; let mut header = header_bar() @@ -384,8 +421,8 @@ impl Application for Window { .on_drag(Message::Drag) .start( nav_button("Settings") - .on_sidebar_toggled(sidebar_message) - .sidebar_active(sidebar_toggled) + .on_nav_bar_toggled(nav_bar_message) + .nav_bar_active(nav_bar_toggled) .into(), ); @@ -401,64 +438,18 @@ impl Application for Window { let mut widgets = Vec::with_capacity(2); - if sidebar_toggled { - let sidebar_button_complex = |page: Page, active| { - cosmic::nav_button!(page.icon_name(), page.title(), active) - .on_press(Message::Page(page)) - .id(BTN.clone()) - }; - - let sidebar_button = |page: Page| sidebar_button_complex(page, self.page == page); - - let mut sidebar = container(scrollable( - column!( - sidebar_button(Page::Demo), - sidebar_button(Page::WiFi), - sidebar_button_complex( - Page::Networking(None), - matches!(self.page, Page::Networking(_)) - ), - sidebar_button(Page::Bluetooth), - sidebar_button_complex( - Page::Desktop(None), - matches!(self.page, Page::Desktop(_)) - ), - sidebar_button_complex( - Page::InputDevices(None), - matches!(self.page, Page::InputDevices(_)) - ), - sidebar_button(Page::Displays), - sidebar_button(Page::PowerAndBattery), - sidebar_button(Page::Sound), - sidebar_button(Page::PrintersAndScanners), - sidebar_button(Page::PrivacyAndSecurity), - sidebar_button_complex( - Page::SystemAndAccounts(None), - matches!(self.page, Page::SystemAndAccounts(_)) - ), - sidebar_button(Page::UpdatesAndRecovery), - sidebar_button_complex( - Page::TimeAndLanguage(None), - matches!(self.page, Page::TimeAndLanguage(_)) - ), - sidebar_button(Page::Accessibility), - sidebar_button(Page::Applications), - ) - .spacing(14), - )) - .height(Length::Fill) - .padding(8) - .style(theme::Container::Custom(nav_bar::nav_bar_sections_style)); + if nav_bar_toggled { + let mut nav_bar = nav_bar(&self.nav_bar_pages, Message::NavBar); if !self.is_condensed() { - sidebar = sidebar.max_width(300) + nav_bar = nav_bar.max_width(300); } - let sidebar: Element<_> = sidebar.into(); - widgets.push(sidebar.debug(self.debug)); + let nav_bar: Element<_> = nav_bar.into(); + widgets.push(nav_bar.debug(self.debug)); } - if !(self.is_condensed() && sidebar_toggled) { + if !(self.is_condensed() && nav_bar_toggled) { let content: Element<_> = match self.page { Page::Demo => self.demo.view(self).map(Message::Demo), Page::Networking(None) => settings::view_column(vec![ diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index a5f96fc..e975595 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -65,29 +65,20 @@ impl Default for State { slider_value: 50.0, spin_button: SpinButtonModel::default().min(-10).max(10), toggler_value: false, - icon_theme: { - let mut icon_theme = segmented_button::State::default(); - let key = icon_theme.insert("Pop", "Pop"); - icon_theme.activate(key); - icon_theme.insert("Adwaita", "Adwaita"); - icon_theme - }, - selection: { - let mut selection = segmented_button::State::default(); - let key = selection.insert("Choice A", ()); - selection.activate(key); - selection.insert("Choice B", ()); - selection.insert("Choice C", ()); - selection - }, - view_switcher: { - let mut view_switcher = segmented_button::State::default(); - let key = view_switcher.insert("Controls", DemoView::TabA); - view_switcher.activate(key); - view_switcher.insert("Segmented Button", DemoView::TabB); - view_switcher.insert("Tab C", DemoView::TabC); - view_switcher - }, + icon_theme: segmented_button::State::builder() + .insert_active("Pop", "Pop") + .insert("Adwaita", "Adwaita") + .build(), + selection: segmented_button::State::builder() + .insert_active("Choice A", ()) + .insert("Choice B", ()) + .insert("Choice C", ()) + .build(), + view_switcher: segmented_button::State::builder() + .insert_active("Controls", DemoView::TabA) + .insert("Segmented Button", DemoView::TabB) + .insert("Tab C", DemoView::TabC) + .build(), } } } diff --git a/src/widget/nav_button.rs b/src/widget/nav_button.rs index de2ba30..ea5f596 100644 --- a/src/widget/nav_button.rs +++ b/src/widget/nav_button.rs @@ -9,17 +9,17 @@ use iced::{alignment::Vertical, Length}; #[derive(Setters)] pub struct NavButton<'a, Message> { title: &'a str, - sidebar_active: bool, + nav_bar_active: bool, #[setters(strip_option)] - on_sidebar_toggled: Option, + on_nav_bar_toggled: Option, } #[must_use] pub fn nav_button(title: &str) -> NavButton { NavButton { title, - sidebar_active: false, - on_sidebar_toggled: None, + nav_bar_active: false, + on_nav_bar_toggled: None, } } @@ -32,7 +32,7 @@ impl<'a, Message: 'static + Clone> From> for Element<'a, .height(Length::Fill); let icon = super::icon( - if nav_button.sidebar_active { + if nav_button.nav_bar_active { "go-previous-symbolic" } else { "go-next-symbolic" @@ -50,7 +50,7 @@ impl<'a, Message: 'static + Clone> From> for Element<'a, .apply(iced::widget::button) .style(theme::Button::Secondary); - if let Some(message) = nav_button.on_sidebar_toggled.clone() { + if let Some(message) = nav_button.on_nav_bar_toggled.clone() { widget = widget.on_press(message); } diff --git a/src/widget/navigation/macros.rs b/src/widget/navigation/macros.rs deleted file mode 100644 index 56bef9d..0000000 --- a/src/widget/navigation/macros.rs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -pub mod nav_bar { - use crate::Theme; - use iced::{widget, Background, Color}; - - #[macro_export] - macro_rules! nav_button { - ($icon: expr, $title:expr, $active:expr) => {{ - $crate::iced::widget::Button::new( - $crate::iced::widget::row!( - $crate::widget::icon($icon, 16) - .style(if $active { - $crate::theme::Svg::SymbolicLink - } else { - $crate::theme::Svg::Symbolic - }), - $crate::iced::widget::Text::new($title) - .vertical_alignment($crate::iced::alignment::Vertical::Center), - $crate::iced::widget::horizontal_space($crate::iced::Length::Fill), - ) - .spacing(8) - ) - .padding([10, 16]) - .style(if $active { - $crate::theme::Button::LinkActive - } else { - $crate::theme::Button::Text - }) - }}; - } - - 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()), - background: Some(Background::Color(cosmic.base.into())), - border_radius: 8.0, - border_width: 0.0, - border_color: Color::TRANSPARENT, - } - } - - 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; -} diff --git a/src/widget/navigation/mod.rs b/src/widget/navigation/mod.rs deleted file mode 100644 index f8fcdec..0000000 --- a/src/widget/navigation/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -pub mod navbar; -pub use navbar::*; - -pub mod macros; -pub use macros::*; diff --git a/src/widget/navigation/navbar.rs b/src/widget/navigation/navbar.rs deleted file mode 100644 index 327c24f..0000000 --- a/src/widget/navigation/navbar.rs +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style}; -use crate::widget::{icon, scrollable}; -use crate::{theme, Renderer, Theme}; -use derive_setters::Setters; -use iced::{Background, Length}; -use iced_core::BorderRadius; -use iced_lazy::Component; -use iced_native::widget::{button, column, container, text}; -use iced_native::{row, Alignment, Element}; -use iced_style::button::Appearance; -use std::collections::BTreeMap; - -#[derive(Setters, Default)] -pub struct NavBar<'a, Message> { - source: BTreeMap>, - active: bool, - condensed: bool, - on_page_selected: Option Message + 'a>>, -} - -impl<'a, Message> NavBar<'a, Message> { - #[must_use] - pub fn new() -> Self { - Self { - source: BTreeMap::default(), - active: false, - condensed: false, - on_page_selected: None, - } - } -} - -#[must_use] -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 { - #[must_use] - pub fn new() -> Self { - Self::default() - } -} - -#[must_use] -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 { - #[must_use] - pub fn new() -> Self { - Self { - title: String::new(), - } - } -} - -#[must_use] -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, - page_active: bool, -} - -impl<'a, Message> Component for NavBar<'a, Message> { - type State = NavBarState; - type Event = NavBarEvent; - - fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option { - match event { - NavBarEvent::SectionSelected(section) => { - if state.selected_section == section { - state.section_active = !state.section_active; - } else { - state.selected_section = section; - state.section_active = true; - } - 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 view(&self, state: &Self::State) -> Element<'a, Self::Event, Renderer> { - if self.active { - let mut sections: Vec> = vec![]; - let mut pages: Vec> = vec![]; - - for (section, section_pages) in &self.source { - sections.push( - button( - column(vec![ - icon(section.icon.clone(), 20).into(), - text(section.title.clone()).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 - } else { - theme::Button::Text - }, - ) - .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 - } else { - theme::Button::Text - } - } else { - theme::Button::Text - }) - .on_press(NavBarEvent::PageSelected(section.clone(), page.clone())) - .into(), - ); - } - } - } - - let nav_bar: Element = - 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: 'static> From> for Element<'a, Message, Renderer> { - fn from(nav_bar: NavBar<'a, Message>) -> Self { - iced_lazy::component(nav_bar) - } -} - -#[must_use] -pub fn section_button_style(theme: &Theme) -> Appearance { - let primary = &theme.cosmic().primary; - Appearance { - shadow_offset: iced::Vector::default(), - background: Some(Background::Color(primary.base.into())), - border_radius: BorderRadius::from(5.0), - border_width: 0.0, - border_color: iced::Color::default(), - text_color: iced::Color::default(), - } -}