Merge branch 'cosmic-design-system' into sctk-cosmic-design-system
This commit is contained in:
commit
9796fa9f15
34 changed files with 850 additions and 1360 deletions
|
|
@ -22,6 +22,7 @@ lazy_static = "1.4.0"
|
||||||
palette = "0.6.1"
|
palette = "0.6.1"
|
||||||
cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", default-features = false, optional = true }
|
cosmic-panel-config = {git = "https://github.com/pop-os/cosmic-panel", default-features = false, optional = true }
|
||||||
sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", optional = true }
|
sctk = { package = "smithay-client-toolkit", git = "https://github.com/Smithay/client-toolkit", optional = true }
|
||||||
|
static-rc = "0.6.1"
|
||||||
|
|
||||||
[dependencies.cosmic-theme]
|
[dependencies.cosmic-theme]
|
||||||
git = "https://github.com/pop-os/cosmic-theme.git"
|
git = "https://github.com/pop-os/cosmic-theme.git"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use cosmic::{
|
||||||
};
|
};
|
||||||
|
|
||||||
mod window;
|
mod window;
|
||||||
pub use window::*;
|
pub use window::Window;
|
||||||
|
|
||||||
pub fn main() -> cosmic::iced::Result {
|
pub fn main() -> cosmic::iced::Result {
|
||||||
let mut settings = settings();
|
let mut settings = settings();
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
use cosmic::widget::{expander, image_icon, nav_bar, nav_bar_page, nav_bar_section};
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::widget::{
|
|
||||||
checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
iced::{self, Alignment, Application, Color, Command, Length},
|
|
||||||
iced_lazy::responsive,
|
|
||||||
iced_native::window,
|
iced_native::window,
|
||||||
list_view, list_view_item, list_view_row, list_view_section, scrollable,
|
iced::widget::{
|
||||||
theme::{self, Button, Theme},
|
column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
||||||
widget::{button, header_bar, list_box, list_row, list_view::*, toggler},
|
},
|
||||||
|
iced::{self, Alignment, Application, Command, Length},
|
||||||
|
iced_lazy::responsive,
|
||||||
|
theme::{self, Theme},
|
||||||
|
widget::{button, nav_button, nav_bar, nav_bar_page, nav_bar_section, header_bar, settings, scrollable, toggler},
|
||||||
Element,
|
Element,
|
||||||
|
ElementExt,
|
||||||
};
|
};
|
||||||
use iced_sctk::application::SurfaceIdWrapper;
|
use iced_sctk::application::SurfaceIdWrapper;
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, vec};
|
||||||
|
use theme::Button as ButtonTheme;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
|
title: String,
|
||||||
page: u8,
|
page: u8,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
|
|
@ -64,6 +67,7 @@ pub enum Message {
|
||||||
Drag,
|
Drag,
|
||||||
Minimize,
|
Minimize,
|
||||||
Maximize,
|
Maximize,
|
||||||
|
InputChanged,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Window {
|
impl Application for Window {
|
||||||
|
|
@ -80,11 +84,12 @@ impl Application for Window {
|
||||||
window.slider_value = 50.0;
|
window.slider_value = 50.0;
|
||||||
// window.theme = Theme::Light;
|
// window.theme = Theme::Light;
|
||||||
window.pick_list_selected = Some("Option 1");
|
window.pick_list_selected = Some("Option 1");
|
||||||
|
window.title = String::from("COSMIC Design System - Iced");
|
||||||
(window, Command::none())
|
(window, Command::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("COSMIC Design System - Iced")
|
self.title.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||||
|
|
@ -94,43 +99,46 @@ impl Application for Window {
|
||||||
Message::ThemeChanged(theme) => self.theme = theme,
|
Message::ThemeChanged(theme) => self.theme = theme,
|
||||||
Message::ButtonPressed => {}
|
Message::ButtonPressed => {}
|
||||||
Message::SliderChanged(value) => self.slider_value = value,
|
Message::SliderChanged(value) => self.slider_value = value,
|
||||||
Message::CheckboxToggled(value) => self.checkbox_value = value,
|
Message::CheckboxToggled(value) => {
|
||||||
|
self.checkbox_value = value;
|
||||||
|
},
|
||||||
Message::TogglerToggled(value) => self.toggler_value = value,
|
Message::TogglerToggled(value) => self.toggler_value = value,
|
||||||
Message::PickListSelected(value) => self.pick_list_selected = Some(value),
|
Message::PickListSelected(value) => self.pick_list_selected = Some(value),
|
||||||
Message::Close => self.exit = true,
|
Message::Close => self.exit = true,
|
||||||
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
|
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
|
||||||
// Message::Drag => return drag(window::Id::new(0)),
|
Message::Drag => todo!(),
|
||||||
// Message::Minimize => return minimize(window::Id::new(0), true),
|
Message::Minimize => todo!(),
|
||||||
// Message::Maximize => return maximize(window::Id::new(0), true),
|
Message::Maximize => todo!(),
|
||||||
Message::RowSelected(row) => println!("Selected row {row}"),
|
Message::RowSelected(row) => println!("Selected row {row}"),
|
||||||
_ => {}
|
Message::InputChanged => {},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn close_requested(&self, _: SurfaceIdWrapper) -> Self::Message {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, _: SurfaceIdWrapper) -> Element<Message> {
|
fn view(&self, _: SurfaceIdWrapper) -> Element<Message> {
|
||||||
let mut header: Element<Message> = header_bar()
|
let mut header = header_bar()
|
||||||
.title(self.title())
|
.title("COSMIC Design System - Iced")
|
||||||
.nav_title(String::from("Settings"))
|
|
||||||
.sidebar_active(self.sidebar_toggled)
|
|
||||||
.show_minimize(self.show_minimize)
|
|
||||||
.show_maximize(self.show_maximize)
|
|
||||||
.on_close(Message::Close)
|
.on_close(Message::Close)
|
||||||
.on_drag(Message::Drag)
|
.on_drag(Message::Drag)
|
||||||
.on_maximize(Message::Maximize)
|
.start(
|
||||||
.on_minimize(Message::Minimize)
|
nav_button("Settings")
|
||||||
.on_sidebar_toggle(Message::ToggleSidebar)
|
.on_sidebar_toggled(Message::ToggleSidebar)
|
||||||
.into();
|
.sidebar_active(self.sidebar_toggled)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
|
||||||
if self.debug {
|
if self.show_maximize {
|
||||||
header = header.explain(Color::WHITE);
|
header = header.on_maximize(Message::Maximize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.show_minimize {
|
||||||
|
header = header.on_minimize(Message::Minimize);
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = Into::<Element<Message>>::into(header).debug(self.debug);
|
||||||
|
|
||||||
// TODO: Adding responsive makes this regenerate on every size change, and regeneration
|
// TODO: Adding responsive makes this regenerate on every size change, and regeneration
|
||||||
// involves allocations for many different items. Ideally, we could only make the nav bar
|
// involves allocations for many different items. Ideally, we could only make the nav bar
|
||||||
// responsive and leave the content to be sized normally.
|
// responsive and leave the content to be sized normally.
|
||||||
|
|
@ -138,26 +146,26 @@ impl Application for Window {
|
||||||
let condensed = size.width < 900.0;
|
let condensed = size.width < 900.0;
|
||||||
|
|
||||||
// cosmic::navbar![
|
// cosmic::navbar![
|
||||||
// nav_button!("network-wireless", "Network & Wireless", condensed)
|
// nav_text_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
|
// ButtonTheme::Primary
|
||||||
// } else {
|
// } else {
|
||||||
// theme::Button::Text
|
// ButtonTheme::Text
|
||||||
// }),
|
// }),
|
||||||
// nav_button!("preferences-desktop", "Bluetooth", condensed)
|
// nav_text_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
|
// ButtonTheme::Primary
|
||||||
// } else {
|
// } else {
|
||||||
// theme::Button::Text
|
// ButtonTheme::Text
|
||||||
// }),
|
// }),
|
||||||
// nav_button!("system-software-update", "Personalization", condensed)
|
// nav_text_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
|
// ButtonTheme::Primary
|
||||||
// } else {
|
// } else {
|
||||||
// theme::Button::Text
|
// ButtonTheme::Text
|
||||||
// }),
|
// }),
|
||||||
// ]
|
// ]
|
||||||
|
|
||||||
|
|
@ -221,49 +229,48 @@ impl Application for Window {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let content: Element<_> = list_view!(
|
let content: Element<_> = settings::view_column(vec![
|
||||||
list_view_section!(
|
settings::view_section("Debug")
|
||||||
"Debug",
|
.add(settings::item("Debug theme", choose_theme))
|
||||||
list_view_item!("Debug theme", choose_theme),
|
.add(settings::item(
|
||||||
list_view_item!(
|
|
||||||
"Debug layout",
|
"Debug layout",
|
||||||
toggler(String::from("Debug layout"), self.debug, Message::Debug,)
|
toggler(String::from("Debug layout"), self.debug, Message::Debug)
|
||||||
)
|
))
|
||||||
),
|
.into(),
|
||||||
list_view_section!(
|
settings::view_section("Buttons")
|
||||||
"Buttons",
|
.add(settings::item_row(vec![
|
||||||
list_view_row!(
|
button(ButtonTheme::Primary)
|
||||||
button!("Primary")
|
.text("Primary")
|
||||||
.style(theme::Button::Primary)
|
.on_press(Message::ButtonPressed)
|
||||||
.on_press(Message::ButtonPressed),
|
.into(),
|
||||||
button!("Secondary")
|
button(ButtonTheme::Secondary)
|
||||||
.style(theme::Button::Secondary)
|
.text("Secondary")
|
||||||
.on_press(Message::ButtonPressed),
|
.on_press(Message::ButtonPressed)
|
||||||
button!("Positive")
|
.into(),
|
||||||
.style(theme::Button::Positive)
|
button(ButtonTheme::Positive)
|
||||||
.on_press(Message::ButtonPressed),
|
.text("Positive")
|
||||||
button!("Destructive")
|
.on_press(Message::ButtonPressed)
|
||||||
.style(theme::Button::Destructive)
|
.into(),
|
||||||
.on_press(Message::ButtonPressed),
|
button(ButtonTheme::Destructive)
|
||||||
button!("Text")
|
.text("Destructive")
|
||||||
.style(theme::Button::Text)
|
.on_press(Message::ButtonPressed)
|
||||||
.on_press(Message::ButtonPressed),
|
.into(),
|
||||||
),
|
button(ButtonTheme::Text)
|
||||||
list_view_row!(
|
.text("Text")
|
||||||
button!("Primary").style(theme::Button::Primary),
|
.on_press(Message::ButtonPressed)
|
||||||
button!("Secondary").style(theme::Button::Secondary),
|
.into()
|
||||||
button!("Positive").style(theme::Button::Positive),
|
]))
|
||||||
button!("Destructive").style(theme::Button::Destructive),
|
.add(settings::item_row(vec![
|
||||||
button!("Text").style(theme::Button::Text),
|
button(ButtonTheme::Primary).text("Primary").into(),
|
||||||
),
|
button(ButtonTheme::Secondary).text("Secondary").into(),
|
||||||
),
|
button(ButtonTheme::Positive).text("Positive").into(),
|
||||||
list_view_section!(
|
button(ButtonTheme::Destructive).text("Destructive").into(),
|
||||||
"Controls",
|
button(ButtonTheme::Text).text("Text").into(),
|
||||||
list_view_item!(
|
]))
|
||||||
"Toggler",
|
.into(),
|
||||||
toggler(None, self.toggler_value, Message::TogglerToggled)
|
settings::view_section("Controls")
|
||||||
),
|
.add(settings::item("Toggler", toggler(None, self.toggler_value, Message::TogglerToggled)))
|
||||||
list_view_item!(
|
.add(settings::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",],
|
||||||
|
|
@ -271,72 +278,30 @@ impl Application for Window {
|
||||||
Message::PickListSelected
|
Message::PickListSelected
|
||||||
)
|
)
|
||||||
.padding([8, 0, 8, 16])
|
.padding([8, 0, 8, 16])
|
||||||
),
|
))
|
||||||
list_view_item!(
|
.add(settings::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_view_item!(
|
.add(settings::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))
|
||||||
.height(Length::Units(4))
|
.height(Length::Units(4))
|
||||||
),
|
))
|
||||||
checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled),
|
.into()
|
||||||
),
|
])
|
||||||
list_view_section!(
|
|
||||||
"Expander",
|
|
||||||
expander()
|
|
||||||
.title("Label")
|
|
||||||
.subtitle("Caption")
|
|
||||||
.icon(String::from("edit-paste"))
|
|
||||||
.on_row_selected(Box::new(Message::RowSelected))
|
|
||||||
.rows(vec![
|
|
||||||
list_row()
|
|
||||||
.title("Label")
|
|
||||||
.subtitle("Caption")
|
|
||||||
.icon(String::from("help-about")),
|
|
||||||
list_row().subtitle("Caption").title("Label"),
|
|
||||||
list_row().title("Label")
|
|
||||||
])
|
|
||||||
),
|
|
||||||
list_view_section!(
|
|
||||||
"List Box",
|
|
||||||
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()
|
|
||||||
])
|
|
||||||
.render()
|
|
||||||
),
|
|
||||||
list_view_section!(
|
|
||||||
"image",
|
|
||||||
button!(image_icon("firefox", 64).unwrap()).style(Button::Transparent)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let mut widgets = Vec::with_capacity(2);
|
let mut widgets = Vec::with_capacity(2);
|
||||||
|
|
||||||
widgets.push(if self.debug {
|
widgets.push(sidebar.debug(self.debug));
|
||||||
sidebar.explain(Color::WHITE)
|
|
||||||
} else {
|
|
||||||
sidebar
|
|
||||||
});
|
|
||||||
|
|
||||||
widgets.push(
|
widgets.push(
|
||||||
scrollable!(row![
|
scrollable(row![
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
if self.debug {
|
content.debug(self.debug),
|
||||||
content.explain(Color::WHITE)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
},
|
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
])
|
])
|
||||||
.into(),
|
.into(),
|
||||||
|
|
@ -360,4 +325,8 @@ impl Application for Window {
|
||||||
fn theme(&self) -> Theme {
|
fn theme(&self) -> Theme {
|
||||||
self.theme
|
self.theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_requested(&self, id: iced_sctk::application::SurfaceIdWrapper) -> Self::Message {
|
||||||
|
Message::Close
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use cosmic::{iced::Application, settings};
|
use cosmic::{iced::Application, settings};
|
||||||
|
|
||||||
mod window;
|
mod window;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
use cosmic::widget::{expander, nav_bar, nav_bar_page, nav_bar_section};
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::widget::{
|
|
||||||
checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
iced::{self, Alignment, Application, Color, Command, Length},
|
|
||||||
iced_lazy::responsive,
|
|
||||||
iced_native::window,
|
iced_native::window,
|
||||||
iced_winit::window::{drag, maximize, minimize},
|
iced::widget::{
|
||||||
list_view, list_view_item, list_view_row, list_view_section, scrollable,
|
column, container, horizontal_space, pick_list, progress_bar, radio, row, slider,
|
||||||
|
},
|
||||||
|
iced::{self, Alignment, Application, Command, Length},
|
||||||
|
iced_lazy::responsive,
|
||||||
|
iced_winit::window::{drag, toggle_maximize, minimize},
|
||||||
theme::{self, Theme},
|
theme::{self, Theme},
|
||||||
widget::{button, header_bar, list_box, list_row, list_view::*, toggler},
|
widget::{button, nav_button, nav_bar, nav_bar_page, nav_bar_section, header_bar, settings, scrollable, toggler},
|
||||||
Element,
|
Element,
|
||||||
|
ElementExt,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, vec};
|
||||||
use cosmic::widget::widget::text_input::Id as TextInputId;
|
use theme::Button as ButtonTheme;
|
||||||
use cosmic::widget::widget::text_input;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
|
title: String,
|
||||||
page: u8,
|
page: u8,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
|
|
@ -83,11 +84,12 @@ impl Application for Window {
|
||||||
window.slider_value = 50.0;
|
window.slider_value = 50.0;
|
||||||
// window.theme = Theme::Light;
|
// window.theme = Theme::Light;
|
||||||
window.pick_list_selected = Some("Option 1");
|
window.pick_list_selected = Some("Option 1");
|
||||||
|
window.title = String::from("COSMIC Design System - Iced");
|
||||||
(window, Command::none())
|
(window, Command::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
String::from("COSMIC Design System - Iced")
|
self.title.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||||
|
|
@ -99,7 +101,6 @@ impl Application for Window {
|
||||||
Message::SliderChanged(value) => self.slider_value = value,
|
Message::SliderChanged(value) => self.slider_value = value,
|
||||||
Message::CheckboxToggled(value) => {
|
Message::CheckboxToggled(value) => {
|
||||||
self.checkbox_value = value;
|
self.checkbox_value = value;
|
||||||
return text_input::focus(TextInputId::new("launcher_entry"));
|
|
||||||
},
|
},
|
||||||
Message::TogglerToggled(value) => self.toggler_value = value,
|
Message::TogglerToggled(value) => self.toggler_value = value,
|
||||||
Message::PickListSelected(value) => self.pick_list_selected = Some(value),
|
Message::PickListSelected(value) => self.pick_list_selected = Some(value),
|
||||||
|
|
@ -107,7 +108,7 @@ impl Application for Window {
|
||||||
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
|
Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled,
|
||||||
Message::Drag => return drag(window::Id::new(0)),
|
Message::Drag => return drag(window::Id::new(0)),
|
||||||
Message::Minimize => return minimize(window::Id::new(0), true),
|
Message::Minimize => return minimize(window::Id::new(0), true),
|
||||||
Message::Maximize => return maximize(window::Id::new(0), true),
|
Message::Maximize => return toggle_maximize(window::Id::new(0)),
|
||||||
Message::RowSelected(row) => println!("Selected row {row}"),
|
Message::RowSelected(row) => println!("Selected row {row}"),
|
||||||
Message::InputChanged => {},
|
Message::InputChanged => {},
|
||||||
|
|
||||||
|
|
@ -117,23 +118,27 @@ impl Application for Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn view(&self) -> Element<Message> {
|
||||||
let mut header: Element<Message> = header_bar()
|
let mut header = header_bar()
|
||||||
.title(self.title())
|
.title("COSMIC Design System - Iced")
|
||||||
.nav_title(String::from("Settings"))
|
|
||||||
.sidebar_active(self.sidebar_toggled)
|
|
||||||
.show_minimize(self.show_minimize)
|
|
||||||
.show_maximize(self.show_maximize)
|
|
||||||
.on_close(Message::Close)
|
.on_close(Message::Close)
|
||||||
.on_drag(Message::Drag)
|
.on_drag(Message::Drag)
|
||||||
.on_maximize(Message::Maximize)
|
.start(
|
||||||
.on_minimize(Message::Minimize)
|
nav_button("Settings")
|
||||||
.on_sidebar_toggle(Message::ToggleSidebar)
|
.on_sidebar_toggled(Message::ToggleSidebar)
|
||||||
.into();
|
.sidebar_active(self.sidebar_toggled)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
|
||||||
if self.debug {
|
if self.show_maximize {
|
||||||
header = header.explain(Color::WHITE);
|
header = header.on_maximize(Message::Maximize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.show_minimize {
|
||||||
|
header = header.on_minimize(Message::Minimize);
|
||||||
|
}
|
||||||
|
|
||||||
|
let header = Into::<Element<Message>>::into(header).debug(self.debug);
|
||||||
|
|
||||||
// TODO: Adding responsive makes this regenerate on every size change, and regeneration
|
// TODO: Adding responsive makes this regenerate on every size change, and regeneration
|
||||||
// involves allocations for many different items. Ideally, we could only make the nav bar
|
// involves allocations for many different items. Ideally, we could only make the nav bar
|
||||||
// responsive and leave the content to be sized normally.
|
// responsive and leave the content to be sized normally.
|
||||||
|
|
@ -141,26 +146,26 @@ impl Application for Window {
|
||||||
let condensed = size.width < 900.0;
|
let condensed = size.width < 900.0;
|
||||||
|
|
||||||
// cosmic::navbar![
|
// cosmic::navbar![
|
||||||
// nav_button!("network-wireless", "Network & Wireless", condensed)
|
// nav_text_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
|
// ButtonTheme::Primary
|
||||||
// } else {
|
// } else {
|
||||||
// theme::Button::Text
|
// ButtonTheme::Text
|
||||||
// }),
|
// }),
|
||||||
// nav_button!("preferences-desktop", "Bluetooth", condensed)
|
// nav_text_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
|
// ButtonTheme::Primary
|
||||||
// } else {
|
// } else {
|
||||||
// theme::Button::Text
|
// ButtonTheme::Text
|
||||||
// }),
|
// }),
|
||||||
// nav_button!("system-software-update", "Personalization", condensed)
|
// nav_text_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
|
// ButtonTheme::Primary
|
||||||
// } else {
|
// } else {
|
||||||
// theme::Button::Text
|
// ButtonTheme::Text
|
||||||
// }),
|
// }),
|
||||||
// ]
|
// ]
|
||||||
|
|
||||||
|
|
@ -224,49 +229,48 @@ impl Application for Window {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let content: Element<_> = list_view!(
|
let content: Element<_> = settings::view_column(vec![
|
||||||
list_view_section!(
|
settings::view_section("Debug")
|
||||||
"Debug",
|
.add(settings::item("Debug theme", choose_theme))
|
||||||
list_view_item!("Debug theme", choose_theme),
|
.add(settings::item(
|
||||||
list_view_item!(
|
|
||||||
"Debug layout",
|
"Debug layout",
|
||||||
toggler(String::from("Debug layout"), self.debug, Message::Debug,)
|
toggler(String::from("Debug layout"), self.debug, Message::Debug)
|
||||||
)
|
))
|
||||||
),
|
.into(),
|
||||||
list_view_section!(
|
settings::view_section("Buttons")
|
||||||
"Buttons",
|
.add(settings::item_row(vec![
|
||||||
list_view_row!(
|
button(ButtonTheme::Primary)
|
||||||
button!("Primary")
|
.text("Primary")
|
||||||
.style(theme::Button::Primary)
|
.on_press(Message::ButtonPressed)
|
||||||
.on_press(Message::ButtonPressed),
|
.into(),
|
||||||
button!("Secondary")
|
button(ButtonTheme::Secondary)
|
||||||
.style(theme::Button::Secondary)
|
.text("Secondary")
|
||||||
.on_press(Message::ButtonPressed),
|
.on_press(Message::ButtonPressed)
|
||||||
button!("Positive")
|
.into(),
|
||||||
.style(theme::Button::Positive)
|
button(ButtonTheme::Positive)
|
||||||
.on_press(Message::ButtonPressed),
|
.text("Positive")
|
||||||
button!("Destructive")
|
.on_press(Message::ButtonPressed)
|
||||||
.style(theme::Button::Destructive)
|
.into(),
|
||||||
.on_press(Message::ButtonPressed),
|
button(ButtonTheme::Destructive)
|
||||||
button!("Text")
|
.text("Destructive")
|
||||||
.style(theme::Button::Text)
|
.on_press(Message::ButtonPressed)
|
||||||
.on_press(Message::ButtonPressed),
|
.into(),
|
||||||
),
|
button(ButtonTheme::Text)
|
||||||
list_view_row!(
|
.text("Text")
|
||||||
button!("Primary").style(theme::Button::Primary),
|
.on_press(Message::ButtonPressed)
|
||||||
button!("Secondary").style(theme::Button::Secondary),
|
.into()
|
||||||
button!("Positive").style(theme::Button::Positive),
|
]))
|
||||||
button!("Destructive").style(theme::Button::Destructive),
|
.add(settings::item_row(vec![
|
||||||
button!("Text").style(theme::Button::Text),
|
button(ButtonTheme::Primary).text("Primary").into(),
|
||||||
),
|
button(ButtonTheme::Secondary).text("Secondary").into(),
|
||||||
),
|
button(ButtonTheme::Positive).text("Positive").into(),
|
||||||
list_view_section!(
|
button(ButtonTheme::Destructive).text("Destructive").into(),
|
||||||
"Controls",
|
button(ButtonTheme::Text).text("Text").into(),
|
||||||
list_view_item!(
|
]))
|
||||||
"Toggler",
|
.into(),
|
||||||
toggler(None, self.toggler_value, Message::TogglerToggled)
|
settings::view_section("Controls")
|
||||||
),
|
.add(settings::item("Toggler", toggler(None, self.toggler_value, Message::TogglerToggled)))
|
||||||
list_view_item!(
|
.add(settings::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",],
|
||||||
|
|
@ -274,76 +278,30 @@ impl Application for Window {
|
||||||
Message::PickListSelected
|
Message::PickListSelected
|
||||||
)
|
)
|
||||||
.padding([8, 0, 8, 16])
|
.padding([8, 0, 8, 16])
|
||||||
),
|
))
|
||||||
list_view_item!(
|
.add(settings::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_view_item!(
|
.add(settings::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))
|
||||||
.height(Length::Units(4))
|
.height(Length::Units(4))
|
||||||
),
|
))
|
||||||
checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled),
|
.into()
|
||||||
text_input(
|
])
|
||||||
"Type something...",
|
|
||||||
"",
|
|
||||||
|_| Message::InputChanged,
|
|
||||||
)
|
|
||||||
.padding(8)
|
|
||||||
.size(20)
|
|
||||||
.id(TextInputId::new("launcher_entry"))
|
|
||||||
),
|
|
||||||
list_view_section!(
|
|
||||||
"Expander",
|
|
||||||
expander()
|
|
||||||
.title("Label")
|
|
||||||
.subtitle("Caption")
|
|
||||||
.icon(String::from("edit-paste"))
|
|
||||||
.on_row_selected(Box::new(Message::RowSelected))
|
|
||||||
.rows(vec![
|
|
||||||
list_row()
|
|
||||||
.title("Label")
|
|
||||||
.subtitle("Caption")
|
|
||||||
.icon(String::from("help-about")),
|
|
||||||
list_row().subtitle("Caption").title("Label"),
|
|
||||||
list_row().title("Label")
|
|
||||||
])
|
|
||||||
),
|
|
||||||
list_view_section!(
|
|
||||||
"List Box",
|
|
||||||
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()
|
|
||||||
])
|
|
||||||
.render()
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
let mut widgets = Vec::with_capacity(2);
|
let mut widgets = Vec::with_capacity(2);
|
||||||
|
|
||||||
widgets.push(if self.debug {
|
widgets.push(sidebar.debug(self.debug));
|
||||||
sidebar.explain(Color::WHITE)
|
|
||||||
} else {
|
|
||||||
sidebar
|
|
||||||
});
|
|
||||||
|
|
||||||
widgets.push(
|
widgets.push(
|
||||||
scrollable!(row![
|
scrollable(row![
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
if self.debug {
|
content.debug(self.debug),
|
||||||
content.explain(Color::WHITE)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
},
|
|
||||||
horizontal_space(Length::Fill),
|
horizontal_space(Length::Fill),
|
||||||
])
|
])
|
||||||
.into(),
|
.into(),
|
||||||
|
|
|
||||||
19
src/ext.rs
Normal file
19
src/ext.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use iced::Color;
|
||||||
|
|
||||||
|
pub trait ElementExt {
|
||||||
|
#[must_use]
|
||||||
|
fn debug(self, debug: bool) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> ElementExt for crate::Element<'a, Message> {
|
||||||
|
fn debug(self, debug: bool) -> Self {
|
||||||
|
if debug {
|
||||||
|
self.explain(Color::WHITE)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub use iced::Font;
|
pub use iced::Font;
|
||||||
|
|
||||||
pub const FONT: Font = Font::External {
|
pub const FONT: Font = Font::External {
|
||||||
|
|
|
||||||
17
src/lib.rs
17
src/lib.rs
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub use iced;
|
pub use iced;
|
||||||
pub use iced_lazy;
|
pub use iced_lazy;
|
||||||
pub use iced_native;
|
pub use iced_native;
|
||||||
|
|
@ -11,19 +14,15 @@ pub mod font;
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
pub mod widget;
|
pub mod widget;
|
||||||
|
|
||||||
|
mod ext;
|
||||||
|
pub use ext::ElementExt;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
pub use theme::Theme;
|
pub use theme::Theme;
|
||||||
pub type Renderer = iced::Renderer<Theme>;
|
pub type Renderer = iced::Renderer<Theme>;
|
||||||
pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>;
|
pub type Element<'a, Message> = iced::Element<'a, Message, Renderer>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum WindowMsg {
|
|
||||||
Close,
|
|
||||||
Drag,
|
|
||||||
Minimize,
|
|
||||||
Maximize,
|
|
||||||
ToggleSidebar,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn settings<Flags: Default>() -> iced::Settings<Flags> {
|
pub fn settings<Flags: Default>() -> iced::Settings<Flags> {
|
||||||
let mut settings = iced::Settings::default();
|
let mut settings = iced::Settings::default();
|
||||||
settings.default_font = match font::FONT {
|
settings.default_font = match font::FONT {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use iced_core::{Background, Color};
|
use iced_core::{Background, Color};
|
||||||
|
|
||||||
/// The appearance of a [`Expander`](crate::native::expander::Expander).
|
/// The appearance of a [`Expander`](crate::native::expander::Expander).
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub mod expander;
|
pub mod expander;
|
||||||
pub mod palette;
|
pub mod palette;
|
||||||
|
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::hash::Hasher;
|
||||||
|
|
||||||
pub use self::palette::Palette;
|
pub use self::palette::Palette;
|
||||||
|
|
||||||
use cosmic_theme::Component;
|
use cosmic_theme::Component;
|
||||||
|
|
@ -52,6 +58,7 @@ pub enum Theme {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Theme {
|
impl Theme {
|
||||||
|
#[must_use]
|
||||||
pub fn cosmic(self) -> &'static CosmicTheme {
|
pub fn cosmic(self) -> &'static CosmicTheme {
|
||||||
match self {
|
match self {
|
||||||
Self::Dark => &COSMIC_DARK,
|
Self::Dark => &COSMIC_DARK,
|
||||||
|
|
@ -59,6 +66,7 @@ impl Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn palette(self) -> Palette {
|
pub fn palette(self) -> Palette {
|
||||||
match self {
|
match self {
|
||||||
Self::Dark => Palette::DARK,
|
Self::Dark => Palette::DARK,
|
||||||
|
|
@ -66,6 +74,7 @@ impl Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn extended_palette(&self) -> &self::palette::Extended {
|
pub fn extended_palette(&self) -> &self::palette::Extended {
|
||||||
match self {
|
match self {
|
||||||
Self::Dark => &self::palette::EXTENDED_DARK,
|
Self::Dark => &self::palette::EXTENDED_DARK,
|
||||||
|
|
@ -113,10 +122,11 @@ impl application::StyleSheet for Theme {
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Button {
|
pub enum Button {
|
||||||
|
Deactivated,
|
||||||
|
Destructive,
|
||||||
|
Positive,
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
Positive,
|
|
||||||
Destructive,
|
|
||||||
Text,
|
Text,
|
||||||
Transparent,
|
Transparent,
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +147,7 @@ impl Button {
|
||||||
Button::Destructive => &cosmic.destructive,
|
Button::Destructive => &cosmic.destructive,
|
||||||
Button::Text => &cosmic.secondary.component,
|
Button::Text => &cosmic.secondary.component,
|
||||||
Button::Transparent => &TRANSPARENT_COMPONENT,
|
Button::Transparent => &TRANSPARENT_COMPONENT,
|
||||||
|
Button::Deactivated => &cosmic.secondary.component,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -189,7 +200,11 @@ impl Default for Checkbox {
|
||||||
impl checkbox::StyleSheet for Theme {
|
impl checkbox::StyleSheet for Theme {
|
||||||
type Style = Checkbox;
|
type Style = Checkbox;
|
||||||
|
|
||||||
fn active(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance {
|
fn active(
|
||||||
|
&self,
|
||||||
|
style: &Self::Style,
|
||||||
|
is_checked: bool,
|
||||||
|
) -> checkbox::Appearance {
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
match style {
|
match style {
|
||||||
|
|
@ -220,7 +235,11 @@ impl checkbox::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hovered(&self, style: &Self::Style, is_checked: bool) -> checkbox::Appearance {
|
fn hovered(
|
||||||
|
&self,
|
||||||
|
style: &Self::Style,
|
||||||
|
is_checked: bool,
|
||||||
|
) -> checkbox::Appearance {
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
match style {
|
match style {
|
||||||
|
|
@ -291,7 +310,7 @@ impl expander::StyleSheet for Theme {
|
||||||
|
|
||||||
fn appearance(&self, style: Self::Style) -> expander::Appearance {
|
fn appearance(&self, style: Self::Style) -> expander::Appearance {
|
||||||
match style {
|
match style {
|
||||||
Expander::Default => Default::default(),
|
Expander::Default => expander::Appearance::default(),
|
||||||
Expander::Custom(f) => f(self),
|
Expander::Custom(f) => f(self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +343,7 @@ impl container::StyleSheet for Theme {
|
||||||
|
|
||||||
fn appearance(&self, style: &Self::Style) -> container::Appearance {
|
fn appearance(&self, style: &Self::Style) -> container::Appearance {
|
||||||
match style {
|
match style {
|
||||||
Container::Transparent => Default::default(),
|
Container::Transparent => container::Appearance::default(),
|
||||||
Container::Box => {
|
Container::Box => {
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
|
|
@ -368,7 +387,9 @@ impl slider::StyleSheet for Theme {
|
||||||
|
|
||||||
fn hovered(&self, style: &Self::Style) -> slider::Appearance {
|
fn hovered(&self, style: &Self::Style) -> slider::Appearance {
|
||||||
let mut style = self.active(&style);
|
let mut style = self.active(&style);
|
||||||
style.handle.shape = slider::HandleShape::Circle { radius: 16.0 };
|
style.handle.shape = slider::HandleShape::Circle {
|
||||||
|
radius: 16.0
|
||||||
|
};
|
||||||
style.handle.border_width = 6.0;
|
style.handle.border_width = 6.0;
|
||||||
style.handle.border_color = match self {
|
style.handle.border_color = match self {
|
||||||
Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.1),
|
Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.1),
|
||||||
|
|
@ -444,7 +465,7 @@ impl pick_list::StyleSheet for Theme {
|
||||||
impl radio::StyleSheet for Theme {
|
impl radio::StyleSheet for Theme {
|
||||||
type Style = ();
|
type Style = ();
|
||||||
|
|
||||||
fn active(&self, _style: &Self::Style, _is_checked: bool) -> radio::Appearance {
|
fn active(&self, _style: &Self::Style, is_selected: bool) -> radio::Appearance {
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
radio::Appearance {
|
radio::Appearance {
|
||||||
|
|
@ -456,8 +477,8 @@ impl radio::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hovered(&self, style: &Self::Style, is_checked: bool) -> radio::Appearance {
|
fn hovered(&self, style: &Self::Style, is_selected: bool) -> radio::Appearance {
|
||||||
let active = self.active(&style, is_checked);
|
let active = self.active(&style, is_selected);
|
||||||
let palette = self.extended_palette();
|
let palette = self.extended_palette();
|
||||||
|
|
||||||
radio::Appearance {
|
radio::Appearance {
|
||||||
|
|
@ -474,7 +495,11 @@ impl radio::StyleSheet for Theme {
|
||||||
impl toggler::StyleSheet for Theme {
|
impl toggler::StyleSheet for Theme {
|
||||||
type Style = ();
|
type Style = ();
|
||||||
|
|
||||||
fn active(&self, _style: &Self::Style, is_active: bool) -> toggler::Appearance {
|
fn active(
|
||||||
|
&self,
|
||||||
|
_style: &Self::Style,
|
||||||
|
is_active: bool,
|
||||||
|
) -> toggler::Appearance {
|
||||||
let palette = self.palette();
|
let palette = self.palette();
|
||||||
|
|
||||||
toggler::Appearance {
|
toggler::Appearance {
|
||||||
|
|
@ -497,7 +522,11 @@ impl toggler::StyleSheet for Theme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hovered(&self, style: &Self::Style, is_active: bool) -> toggler::Appearance {
|
fn hovered(
|
||||||
|
&self,
|
||||||
|
style: &Self::Style,
|
||||||
|
is_active: bool,
|
||||||
|
) -> toggler::Appearance {
|
||||||
//TODO: grab colors from palette
|
//TODO: grab colors from palette
|
||||||
match self {
|
match self {
|
||||||
Theme::Dark => toggler::Appearance {
|
Theme::Dark => toggler::Appearance {
|
||||||
|
|
@ -515,7 +544,7 @@ impl toggler::StyleSheet for Theme {
|
||||||
Color::from_rgb8(0x54, 0x54, 0x54)
|
Color::from_rgb8(0x54, 0x54, 0x54)
|
||||||
},
|
},
|
||||||
..self.active(&style, is_active)
|
..self.active(&style, is_active)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -659,23 +688,42 @@ impl scrollable::StyleSheet for Theme {
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy)]
|
#[derive(Default, Clone, Copy)]
|
||||||
pub enum Svg {
|
pub enum Svg {
|
||||||
|
/// Apply a custom appearance filter
|
||||||
Custom(fn(&Theme) -> svg::Appearance),
|
Custom(fn(&Theme) -> svg::Appearance),
|
||||||
|
/// No filtering is applied
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
Accent,
|
/// Icon fill color will match text color
|
||||||
|
Symbolic,
|
||||||
|
/// Icon fill color will match accent color
|
||||||
|
SymbolicActive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Svg {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
let id = match self {
|
||||||
|
Svg::Custom(_) => 0,
|
||||||
|
Svg::Default => 1,
|
||||||
|
Svg::Symbolic => 2,
|
||||||
|
Svg::SymbolicActive => 3
|
||||||
|
};
|
||||||
|
|
||||||
|
id.hash(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl svg::StyleSheet for Theme {
|
impl svg::StyleSheet for Theme {
|
||||||
type Style = Svg;
|
type Style = Svg;
|
||||||
|
|
||||||
fn appearance(&self, style: Self::Style) -> svg::Appearance {
|
fn appearance(&self, style: Self::Style) -> svg::Appearance {
|
||||||
let cosmic = self.cosmic();
|
|
||||||
|
|
||||||
match style {
|
match style {
|
||||||
Svg::Default => Default::default(),
|
Svg::Default => svg::Appearance::default(),
|
||||||
Svg::Custom(appearance) => appearance(self),
|
Svg::Custom(appearance) => appearance(self),
|
||||||
Svg::Accent => svg::Appearance {
|
Svg::Symbolic => svg::Appearance {
|
||||||
fill: Some(cosmic.accent.base.into()),
|
fill: Some(self.extended_palette().background.base.text),
|
||||||
|
},
|
||||||
|
Svg::SymbolicActive => svg::Appearance {
|
||||||
|
fill: Some(self.cosmic().accent.base.into()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -707,7 +755,7 @@ impl text::StyleSheet for Theme {
|
||||||
Text::Accent => text::Appearance {
|
Text::Accent => text::Appearance {
|
||||||
color: Some(self.cosmic().accent.base.into()),
|
color: Some(self.cosmic().accent.base.into()),
|
||||||
},
|
},
|
||||||
Text::Default => Default::default(),
|
Text::Default => text::Appearance::default(),
|
||||||
Text::Color(c) => text::Appearance { color: Some(c) },
|
Text::Color(c) => text::Appearance { color: Some(c) },
|
||||||
Text::Custom(f) => f(self),
|
Text::Custom(f) => f(self),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//TODO: GET CORRECT PALETTE FROM COSMIC-THEME
|
//TODO: GET CORRECT PALETTE FROM COSMIC-THEME
|
||||||
use iced_core::Color;
|
use iced_core::Color;
|
||||||
|
|
||||||
|
|
@ -85,6 +88,7 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Extended {
|
impl Extended {
|
||||||
|
#[must_use]
|
||||||
pub fn generate(palette: Palette) -> Self {
|
pub fn generate(palette: Palette) -> Self {
|
||||||
Self {
|
Self {
|
||||||
background: Background::new(palette.background, palette.text),
|
background: Background::new(palette.background, palette.text),
|
||||||
|
|
@ -118,6 +122,7 @@ pub struct Background {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Background {
|
impl Background {
|
||||||
|
#[must_use]
|
||||||
pub fn new(base: Color, text: Color) -> Self {
|
pub fn new(base: Color, text: Color) -> Self {
|
||||||
let weak = mix(base, text, 0.15);
|
let weak = mix(base, text, 0.15);
|
||||||
let strong = mix(base, text, 0.40);
|
let strong = mix(base, text, 0.40);
|
||||||
|
|
@ -137,6 +142,7 @@ pub struct Primary {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primary {
|
impl Primary {
|
||||||
|
#[must_use]
|
||||||
pub fn generate(base: Color, background: Color, text: Color) -> Self {
|
pub fn generate(base: Color, background: Color, text: Color) -> Self {
|
||||||
let weak = mix(base, background, 0.4);
|
let weak = mix(base, background, 0.4);
|
||||||
let strong = deviate(base, 0.1);
|
let strong = deviate(base, 0.1);
|
||||||
|
|
@ -156,6 +162,7 @@ pub struct Secondary {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Secondary {
|
impl Secondary {
|
||||||
|
#[must_use]
|
||||||
pub fn generate(base: Color, text: Color) -> Self {
|
pub fn generate(base: Color, text: Color) -> Self {
|
||||||
let base = mix(base, text, 0.2);
|
let base = mix(base, text, 0.2);
|
||||||
let weak = mix(base, text, 0.1);
|
let weak = mix(base, text, 0.1);
|
||||||
|
|
@ -176,6 +183,7 @@ pub struct Success {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Success {
|
impl Success {
|
||||||
|
#[must_use]
|
||||||
pub fn generate(base: Color, background: Color, text: Color) -> Self {
|
pub fn generate(base: Color, background: Color, text: Color) -> Self {
|
||||||
let weak = mix(base, background, 0.4);
|
let weak = mix(base, background, 0.4);
|
||||||
let strong = deviate(base, 0.1);
|
let strong = deviate(base, 0.1);
|
||||||
|
|
@ -195,6 +203,7 @@ pub struct Danger {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Danger {
|
impl Danger {
|
||||||
|
#[must_use]
|
||||||
pub fn generate(base: Color, background: Color, text: Color) -> Self {
|
pub fn generate(base: Color, background: Color, text: Color) -> Self {
|
||||||
let weak = mix(base, background, 0.4);
|
let weak = mix(base, background, 0.4);
|
||||||
let strong = deviate(base, 0.1);
|
let strong = deviate(base, 0.1);
|
||||||
|
|
|
||||||
9
src/utils.rs
Normal file
9
src/utils.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use static_rc::StaticRc;
|
||||||
|
|
||||||
|
/// Uses [`StaticRc`] to create two halves of value with shared ownership, with no runtime reference counting required.
|
||||||
|
pub(crate) fn static_rc_halves<T>(value: T) -> (StaticRc<T, 1, 3>, StaticRc<T, 2, 3>) {
|
||||||
|
StaticRc::split::<1, 2>(StaticRc::<T, 3, 3>::new(value))
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,50 @@
|
||||||
#[macro_export]
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
macro_rules! button {
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
($($x:expr),+ $(,)?) => (
|
|
||||||
$crate::iced::widget::Button::new(
|
use crate::{theme, Element, Renderer};
|
||||||
$crate::iced::widget::Row::with_children(
|
use iced::widget;
|
||||||
vec![$($crate::iced::Element::from($x)),+]
|
|
||||||
)
|
/// A button widget with COSMIC styling
|
||||||
.spacing(8)
|
#[must_use]
|
||||||
)
|
pub const fn button<Message>(style: theme::Button) -> Button<Message> {
|
||||||
.padding([8, 16])
|
Button { style, message: None }
|
||||||
);
|
|
||||||
}
|
}
|
||||||
pub use button;
|
|
||||||
|
/// A button widget with COSMIC styling
|
||||||
|
pub struct Button<Message> {
|
||||||
|
style: theme::Button,
|
||||||
|
message: Option<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message: 'static> Button<Message> {
|
||||||
|
/// The message to emit on button press.
|
||||||
|
#[must_use]
|
||||||
|
pub fn on_press(mut self, message: Message) -> Self {
|
||||||
|
self.message = Some(message);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A button with an icon.
|
||||||
|
pub fn icon(self, style: theme::Svg, icon: &str, size: u16) -> widget::Button<Message, Renderer> {
|
||||||
|
self.custom(vec![super::icon(icon, size).style(style).into()])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A button with text.
|
||||||
|
pub fn text(self, text: &str) -> widget::Button<Message, Renderer> {
|
||||||
|
self.custom(vec![text.into()])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A custom button that has the desired default spacing and padding.
|
||||||
|
pub fn custom(self, children: Vec<Element<Message>>) -> widget::Button<Message, Renderer> {
|
||||||
|
let button = widget::button(widget::row(children).spacing(8))
|
||||||
|
.style(self.style)
|
||||||
|
.padding([8, 16]);
|
||||||
|
|
||||||
|
if let Some(message) = self.message {
|
||||||
|
button.on_press(message)
|
||||||
|
} else {
|
||||||
|
button
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,209 +0,0 @@
|
||||||
use std::vec;
|
|
||||||
|
|
||||||
use crate::{list_box_row, separator, theme, widget::ListRow, Element, Renderer, Theme};
|
|
||||||
use apply::Apply;
|
|
||||||
use derive_setters::Setters;
|
|
||||||
use iced::{
|
|
||||||
widget::{self, button, container, horizontal_space, row, text, Column},
|
|
||||||
Alignment, Background, Length,
|
|
||||||
};
|
|
||||||
use iced_lazy::Component;
|
|
||||||
use iced_native::widget::{column, event_container};
|
|
||||||
|
|
||||||
#[derive(Setters)]
|
|
||||||
pub struct Expander<'a, Message> {
|
|
||||||
title: &'a str,
|
|
||||||
#[setters(strip_option)]
|
|
||||||
subtitle: Option<&'a str>,
|
|
||||||
#[setters(strip_option)]
|
|
||||||
icon: Option<String>,
|
|
||||||
expansible: bool,
|
|
||||||
#[setters(skip)]
|
|
||||||
rows: Option<Vec<ListRow<'a>>>,
|
|
||||||
#[setters(strip_option)]
|
|
||||||
on_row_selected: Option<Box<dyn Fn(usize) -> Message + 'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expander<'a, Message>() -> Expander<'a, Message> {
|
|
||||||
Expander {
|
|
||||||
title: "",
|
|
||||||
subtitle: None,
|
|
||||||
icon: None,
|
|
||||||
expansible: false,
|
|
||||||
rows: None,
|
|
||||||
on_row_selected: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExpanderState {
|
|
||||||
pub expanded: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ExpanderState {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { expanded: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum ExpanderEvent {
|
|
||||||
Expand,
|
|
||||||
RowSelected(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message> Expander<'a, Message> {
|
|
||||||
pub fn rows(mut self, rows: Vec<ListRow<'a>>) -> Self {
|
|
||||||
self.rows = Some(rows);
|
|
||||||
self.expansible = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, row: ListRow<'a>) {
|
|
||||||
if self.rows.is_none() {
|
|
||||||
self.rows = Some(vec![])
|
|
||||||
}
|
|
||||||
self.rows.as_mut().unwrap().push(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'a> Component<Message, Renderer> for Expander<'a, Message> {
|
|
||||||
type State = ExpanderState;
|
|
||||||
|
|
||||||
type Event = ExpanderEvent;
|
|
||||||
|
|
||||||
fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
ExpanderEvent::Expand => {
|
|
||||||
state.expanded = !state.expanded;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
ExpanderEvent::RowSelected(index) => self
|
|
||||||
.on_row_selected
|
|
||||||
.as_ref()
|
|
||||||
.map(|on_row_selected| (on_row_selected)(index)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, state: &Self::State) -> Element<Self::Event> {
|
|
||||||
let heading: Element<ExpanderEvent> = {
|
|
||||||
let mut captions = vec![text(&self.title).size(18).into()];
|
|
||||||
if let Some(subtitle) = &self.subtitle {
|
|
||||||
captions.push(text(subtitle).size(16).into());
|
|
||||||
}
|
|
||||||
let text = column(captions);
|
|
||||||
let space: Element<ExpanderEvent> = horizontal_space(Length::Fill).into();
|
|
||||||
let toggler: Element<ExpanderEvent> = {
|
|
||||||
let mut icon = super::icon(
|
|
||||||
if state.expanded {
|
|
||||||
"go-down-symbolic"
|
|
||||||
} else {
|
|
||||||
"go-next-symbolic"
|
|
||||||
},
|
|
||||||
16,
|
|
||||||
)
|
|
||||||
.apply(button)
|
|
||||||
.width(Length::Units(25));
|
|
||||||
if self.expansible {
|
|
||||||
icon = icon.on_press(ExpanderEvent::Expand);
|
|
||||||
}
|
|
||||||
icon.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let items = if let Some(icon) = &self.icon {
|
|
||||||
let icon = super::icon(icon.as_str(), 20)
|
|
||||||
.apply(event_container)
|
|
||||||
.padding(10);
|
|
||||||
row![icon, text, space, toggler]
|
|
||||||
} else {
|
|
||||||
row![text, space, toggler]
|
|
||||||
};
|
|
||||||
|
|
||||||
container(items.align_items(Alignment::Center))
|
|
||||||
.style(theme::Container::Custom(expander_heading_style))
|
|
||||||
.padding(10)
|
|
||||||
.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let rows: Vec<Element<_>> = if let Some(rows) = &self.rows {
|
|
||||||
rows.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, row)| {
|
|
||||||
let subtitle = row.subtitle.unwrap_or_default();
|
|
||||||
if let Some(icon) = &row.icon {
|
|
||||||
list_box_row!(row.title, subtitle, icon.as_str())
|
|
||||||
.apply(event_container)
|
|
||||||
.on_press(ExpanderEvent::RowSelected(index))
|
|
||||||
.into()
|
|
||||||
} else {
|
|
||||||
list_box_row!(row.title, subtitle)
|
|
||||||
.apply(event_container)
|
|
||||||
.on_press(ExpanderEvent::RowSelected(index))
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(index, child)| {
|
|
||||||
if index != rows.len() - 1 {
|
|
||||||
vec![child, separator!(1).into()]
|
|
||||||
} else {
|
|
||||||
vec![child]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
let rows: Element<ExpanderEvent> = Column::with_children(rows).into();
|
|
||||||
|
|
||||||
let mut layout = vec![heading];
|
|
||||||
if state.expanded && self.expansible {
|
|
||||||
layout.push(rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
column(layout)
|
|
||||||
.apply(widget::container)
|
|
||||||
.height(Length::Shrink)
|
|
||||||
.style(theme::Container::Custom(expander_row_style))
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'a> From<Expander<'a, Message>> for Element<'a, Message> {
|
|
||||||
fn from(expander: Expander<'a, Message>) -> Self {
|
|
||||||
iced_lazy::component(expander)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expander_heading_style(theme: &Theme) -> widget::container::Appearance {
|
|
||||||
let primary = &theme.cosmic().primary;
|
|
||||||
let accent = &theme.cosmic().accent;
|
|
||||||
widget::container::Appearance {
|
|
||||||
text_color: Some(accent.base.into()),
|
|
||||||
background: Some(Background::Color(primary.divider.into())),
|
|
||||||
border_radius: 8.0,
|
|
||||||
border_width: 0.0,
|
|
||||||
border_color: primary.on.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expander_row_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.4,
|
|
||||||
border_color: cosmic.divider.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn separator_style(theme: &Theme) -> widget::rule::Appearance {
|
|
||||||
let cosmic = &theme.cosmic().primary;
|
|
||||||
widget::rule::Appearance {
|
|
||||||
color: cosmic.divider.into(),
|
|
||||||
width: 1,
|
|
||||||
radius: 0.0,
|
|
||||||
fill_mode: widget::rule::FillMode::Padded(10),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
use crate::{theme, Element, Renderer};
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
use derive_setters::*;
|
use derive_setters::Setters;
|
||||||
use iced::{self, alignment::Vertical, widget, Length};
|
use iced::{self, widget, Length};
|
||||||
use iced_lazy::Component;
|
use crate::{theme, Element};
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
||||||
|
HeaderBar {
|
||||||
|
title: "",
|
||||||
|
on_close: None,
|
||||||
|
on_drag: None,
|
||||||
|
on_maximize: None,
|
||||||
|
on_minimize: None,
|
||||||
|
start: None,
|
||||||
|
center: None,
|
||||||
|
end: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Setters)]
|
#[derive(Setters)]
|
||||||
pub struct HeaderBar<Message> {
|
pub struct HeaderBar<'a, Message> {
|
||||||
title: String,
|
title: &'a str,
|
||||||
nav_title: String,
|
|
||||||
sidebar_active: bool,
|
|
||||||
show_minimize: bool,
|
|
||||||
show_maximize: bool,
|
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
on_close: Option<Message>,
|
on_close: Option<Message>,
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
|
|
@ -20,133 +32,98 @@ pub struct HeaderBar<Message> {
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
on_minimize: Option<Message>,
|
on_minimize: Option<Message>,
|
||||||
#[setters(strip_option)]
|
#[setters(strip_option)]
|
||||||
on_sidebar_toggle: Option<Message>,
|
start: Option<Element<'a, Message>>,
|
||||||
|
#[setters(strip_option)]
|
||||||
|
center: Option<Element<'a, Message>>,
|
||||||
|
#[setters(strip_option)]
|
||||||
|
end: Option<Element<'a, Message>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header_bar<Message>() -> HeaderBar<Message> {
|
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||||
HeaderBar {
|
/// Converts the headerbar builder into an Iced element.
|
||||||
title: String::default(),
|
pub fn into_element(mut self) -> Element<'a, Message> {
|
||||||
nav_title: String::default(),
|
let mut packed: Vec<Element<Message>> = Vec::with_capacity(4);
|
||||||
sidebar_active: false,
|
|
||||||
show_minimize: false,
|
|
||||||
show_maximize: false,
|
|
||||||
on_sidebar_toggle: None,
|
|
||||||
on_close: None,
|
|
||||||
on_drag: None,
|
|
||||||
on_maximize: None,
|
|
||||||
on_minimize: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
if let Some(start) = self.start.take() {
|
||||||
pub enum HeaderEvent {
|
packed.push(widget::container(start).align_x(iced::alignment::Horizontal::Left).into());
|
||||||
Close,
|
|
||||||
ToggleSidebar,
|
|
||||||
Drag,
|
|
||||||
Minimize,
|
|
||||||
Maximize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Message: Clone> Component<Message, Renderer> for HeaderBar<Message> {
|
|
||||||
type State = ();
|
|
||||||
|
|
||||||
type Event = HeaderEvent;
|
|
||||||
|
|
||||||
fn update(&mut self, _state: &mut Self::State, event: Self::Event) -> Option<Message> {
|
|
||||||
match event {
|
|
||||||
HeaderEvent::Close => self.on_close.clone(),
|
|
||||||
|
|
||||||
HeaderEvent::ToggleSidebar => self.on_sidebar_toggle.clone(),
|
|
||||||
|
|
||||||
HeaderEvent::Drag => self.on_drag.clone(),
|
|
||||||
|
|
||||||
HeaderEvent::Maximize => self.on_maximize.clone(),
|
|
||||||
|
|
||||||
HeaderEvent::Minimize => self.on_minimize.clone(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packed.push(if let Some(center) = self.center.take() {
|
||||||
|
widget::container(center).align_x(iced::alignment::Horizontal::Center).into()
|
||||||
|
} else {
|
||||||
|
self.title_widget()
|
||||||
|
});
|
||||||
|
|
||||||
|
packed.push(if let Some(end) = self.end.take() {
|
||||||
|
widget::row(vec![end, self.window_controls()])
|
||||||
|
.apply(widget::container)
|
||||||
|
.align_x(iced::alignment::Horizontal::Right)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
self.window_controls()
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut widget = widget::row(packed)
|
||||||
|
.height(Length::Units(50))
|
||||||
|
.padding(10)
|
||||||
|
.apply(widget::event_container)
|
||||||
|
.center_y();
|
||||||
|
|
||||||
|
if let Some(message) = self.on_drag.clone() {
|
||||||
|
widget = widget.on_press(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = self.on_maximize.clone() {
|
||||||
|
widget = widget.on_release(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, _state: &Self::State) -> Element<Self::Event> {
|
fn title_widget(&self) -> Element<'a, Message> {
|
||||||
let nav_button = {
|
widget::container(widget::text(self.title))
|
||||||
let text = widget::text(&self.nav_title)
|
|
||||||
.style(theme::Text::Accent)
|
|
||||||
.vertical_alignment(Vertical::Center)
|
|
||||||
.width(Length::Shrink)
|
|
||||||
.height(Length::Fill);
|
|
||||||
|
|
||||||
let icon = super::icon(
|
|
||||||
if self.sidebar_active {
|
|
||||||
"go-previous-symbolic"
|
|
||||||
} else {
|
|
||||||
"go-next-symbolic"
|
|
||||||
},
|
|
||||||
24,
|
|
||||||
)
|
|
||||||
.style(theme::Svg::Accent)
|
|
||||||
.width(Length::Units(24))
|
|
||||||
.height(Length::Fill);
|
|
||||||
|
|
||||||
widget::row!(text, icon)
|
|
||||||
.padding(4)
|
|
||||||
.spacing(4)
|
|
||||||
.apply(widget::button)
|
|
||||||
.style(theme::Button::Secondary)
|
|
||||||
.on_press(HeaderEvent::ToggleSidebar)
|
|
||||||
.apply(widget::container)
|
|
||||||
.center_y()
|
|
||||||
.height(Length::Fill)
|
|
||||||
.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = widget::container(widget::text(&self.title))
|
|
||||||
.center_x()
|
.center_x()
|
||||||
.center_y()
|
.center_y()
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.into();
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
let window_controls = {
|
/// Creates the widget for window controls.
|
||||||
let mut widgets: Vec<Element<HeaderEvent>> = Vec::with_capacity(3);
|
fn window_controls(&mut self) -> Element<'a, Message> {
|
||||||
|
let mut widgets: Vec<Element<_>> = Vec::with_capacity(3);
|
||||||
|
|
||||||
let icon = |name, size, on_press| {
|
let icon = |name, size, on_press| {
|
||||||
super::icon(name, size)
|
super::icon(name, size)
|
||||||
.style(crate::theme::Svg::Accent)
|
.style(crate::theme::Svg::SymbolicActive)
|
||||||
.apply(widget::button)
|
.apply(iced::widget::button)
|
||||||
.style(theme::Button::Text)
|
.style(theme::Button::Text)
|
||||||
.on_press(on_press)
|
.on_press(on_press)
|
||||||
};
|
|
||||||
|
|
||||||
if self.show_minimize {
|
|
||||||
widgets.push(icon("window-minimize-symbolic", 16, HeaderEvent::Minimize).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.show_maximize {
|
|
||||||
widgets.push(icon("window-maximize-symbolic", 16, HeaderEvent::Maximize).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
widgets.push(icon("window-close-symbolic", 16, HeaderEvent::Close).into());
|
|
||||||
|
|
||||||
widget::row(widgets)
|
|
||||||
.spacing(8)
|
|
||||||
.apply(widget::container)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.center_y()
|
|
||||||
.into()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
widget::row(vec![nav_button, content, window_controls])
|
if let Some(message) = self.on_minimize.take() {
|
||||||
.height(Length::Units(50))
|
widgets.push(icon("window-minimize-symbolic", 16, message).into());
|
||||||
.padding(10)
|
}
|
||||||
.apply(widget::event_container)
|
|
||||||
|
if let Some(message) = self.on_maximize.take() {
|
||||||
|
widgets.push(icon("window-maximize-symbolic", 16, message).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(message) = self.on_close.take() {
|
||||||
|
widgets.push(icon("window-close-symbolic", 16, message).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
widget::row(widgets)
|
||||||
|
.spacing(8)
|
||||||
|
.apply(widget::container)
|
||||||
|
.height(Length::Fill)
|
||||||
.center_y()
|
.center_y()
|
||||||
.on_press(HeaderEvent::Drag)
|
|
||||||
.on_release(HeaderEvent::Maximize)
|
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: Clone + 'a> From<HeaderBar<Message>> for Element<'a, Message> {
|
impl<'a, Message: Clone + 'static> From<HeaderBar<'a, Message>> for Element<'a, Message> {
|
||||||
fn from(header_bar: HeaderBar<Message>) -> Self {
|
fn from(headerbar: HeaderBar<'a, Message>) -> Self {
|
||||||
iced_lazy::component(header_bar)
|
headerbar.into_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
//! Lazily-generated SVG icon widget for Iced.
|
||||||
|
|
||||||
use iced::{
|
use iced::{
|
||||||
widget::{svg, Image},
|
widget::{svg, Image},
|
||||||
Length,
|
Length, ContentFit,
|
||||||
};
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use derive_setters::Setters;
|
||||||
|
use crate::{Element, Renderer};
|
||||||
|
|
||||||
pub fn icon<Renderer>(name: &str, size: u16) -> svg::Svg<Renderer>
|
/// A lazily-generated SVG icon.
|
||||||
where
|
#[derive(Hash, Setters)]
|
||||||
Renderer: iced_native::svg::Renderer,
|
pub struct Icon<'a> {
|
||||||
Renderer::Theme: iced_native::svg::StyleSheet,
|
#[setters(skip)]
|
||||||
{
|
name: Cow<'a, str>,
|
||||||
let handle = match freedesktop_icons::lookup(name)
|
#[setters(into)]
|
||||||
.with_size(size)
|
theme: Cow<'a, str>,
|
||||||
.with_theme("Pop")
|
style: crate::theme::Svg,
|
||||||
.with_cache()
|
size: u16,
|
||||||
.force_svg()
|
#[setters(strip_option)]
|
||||||
.find()
|
content_fit: Option<ContentFit>,
|
||||||
{
|
#[setters(strip_option)]
|
||||||
Some(path) => svg::Handle::from_path(path),
|
width: Option<Length>,
|
||||||
None => {
|
#[setters(strip_option)]
|
||||||
eprintln!("icon '{}' size {} not found", name, size);
|
height: Option<Length>,
|
||||||
svg::Handle::from_memory(Vec::new())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
svg::Svg::new(handle)
|
|
||||||
.width(Length::Units(size))
|
|
||||||
.height(Length::Units(size))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_icon(name: &str, size: u16) -> Option<Image> {
|
pub fn image_icon(name: &str, size: u16) -> Option<Image> {
|
||||||
|
|
@ -37,3 +40,56 @@ pub fn image_icon(name: &str, size: u16) -> Option<Image> {
|
||||||
.height(Length::Units(size))
|
.height(Length::Units(size))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// A lazily-generated SVG icon.
|
||||||
|
#[must_use]
|
||||||
|
pub fn icon<'a>(name: impl Into<Cow<'a, str>>, size: u16) -> Icon<'a> {
|
||||||
|
Icon {
|
||||||
|
content_fit: None,
|
||||||
|
height: None,
|
||||||
|
name: name.into(),
|
||||||
|
size,
|
||||||
|
style: crate::theme::Svg::default(),
|
||||||
|
theme: Cow::Borrowed("Pop"),
|
||||||
|
width: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Icon<'a> {
|
||||||
|
#[must_use]
|
||||||
|
fn into_svg<Message: 'static>(self) -> Element<'a, Message> {
|
||||||
|
let (svg, svg_clone) = crate::utils::static_rc_halves(self);
|
||||||
|
|
||||||
|
iced_lazy::lazy(svg_clone, move || -> Element<Message> {
|
||||||
|
let icon = freedesktop_icons::lookup(&svg.name)
|
||||||
|
.with_size(svg.size)
|
||||||
|
.with_theme(&svg.theme)
|
||||||
|
.with_cache()
|
||||||
|
.force_svg()
|
||||||
|
.find();
|
||||||
|
|
||||||
|
let handle = if let Some(path) = icon {
|
||||||
|
svg::Handle::from_path(path)
|
||||||
|
} else {
|
||||||
|
eprintln!("icon '{}' size {} not found", svg.name, svg.size);
|
||||||
|
svg::Handle::from_memory(Vec::new())
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut widget = svg::Svg::<Renderer>::new(handle)
|
||||||
|
.style(svg.style)
|
||||||
|
.width(svg.width.unwrap_or(Length::Units(svg.size)))
|
||||||
|
.height(svg.height.unwrap_or(Length::Units(svg.size)));
|
||||||
|
|
||||||
|
if let Some(content_fit) = svg.content_fit {
|
||||||
|
widget = widget.content_fit(content_fit);
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.into()
|
||||||
|
}).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> From<Icon<'a>> for Element<'a, Message> {
|
||||||
|
fn from(icon: Icon<'a>) -> Self {
|
||||||
|
icon.into_svg::<Message>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
65
src/widget/list/column.rs
Normal file
65
src/widget/list/column.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use apply::Apply;
|
||||||
|
use crate::{Element, theme};
|
||||||
|
use crate::widget::horizontal_rule;
|
||||||
|
use iced::{Background, Color};
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn list_column<'a, Message: 'static>() -> ListColumn<'a, Message> {
|
||||||
|
ListColumn::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ListColumn<'a, Message> {
|
||||||
|
children: Vec<Element<'a, Message>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> Default for ListColumn<'a, Message> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { children: Vec::with_capacity(4) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> ListColumn<'a, Message> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
|
||||||
|
if !self.children.is_empty() {
|
||||||
|
self.children.push(horizontal_rule(12).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.children.push(item.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn into_element(self) -> Element<'a, Message> {
|
||||||
|
iced::widget::column(self.children)
|
||||||
|
.spacing(12)
|
||||||
|
.apply(iced::widget::container)
|
||||||
|
.padding([12, 16])
|
||||||
|
.style(theme::Container::Custom(style))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> From<ListColumn<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(column: ListColumn<'a, Message>) -> Self {
|
||||||
|
column.into_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style(theme: &crate::Theme) -> iced::widget::container::Appearance {
|
||||||
|
let cosmic = &theme.cosmic().primary;
|
||||||
|
iced::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/widget/list/item.rs
Normal file
2
src/widget/list/item.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
@ -1,452 +0,0 @@
|
||||||
use crate::separator;
|
|
||||||
use crate::theme::{self, Container};
|
|
||||||
use derive_setters::Setters;
|
|
||||||
use iced::mouse::Interaction;
|
|
||||||
use iced::{overlay, Alignment, Length, Padding, Point, Rectangle};
|
|
||||||
use iced_native::event::Status;
|
|
||||||
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, Operation, Tree};
|
|
||||||
use iced_native::{
|
|
||||||
renderer, row, Background, Clipboard, Color, Element, Event, Layout, Shell, Widget,
|
|
||||||
};
|
|
||||||
use iced_style::container::{Appearance, StyleSheet};
|
|
||||||
|
|
||||||
#[derive(Setters)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct ListBox<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
Renderer: iced_native::Renderer,
|
|
||||||
Renderer::Theme: StyleSheet + iced_style::rule::StyleSheet,
|
|
||||||
<Renderer as iced_native::Renderer>::Theme: iced_style::rule::StyleSheet,
|
|
||||||
{
|
|
||||||
spacing: u16,
|
|
||||||
#[setters(into)]
|
|
||||||
padding: Padding,
|
|
||||||
width: Length,
|
|
||||||
height: Length,
|
|
||||||
max_width: u32,
|
|
||||||
align_items: Alignment,
|
|
||||||
style: <Renderer::Theme as StyleSheet>::Style,
|
|
||||||
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>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_box<'a, Message: 'a, Renderer>() -> ListBox<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
Renderer: iced_native::Renderer + 'a,
|
|
||||||
<<Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style: From<Container>,
|
|
||||||
<Renderer as iced_native::Renderer>::Theme: StyleSheet + iced_style::rule::StyleSheet,
|
|
||||||
<<Renderer as iced_native::Renderer>::Theme as iced_style::rule::StyleSheet>::Style:
|
|
||||||
From<theme::Rule>,
|
|
||||||
{
|
|
||||||
ListBox::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'a, Renderer: iced_native::Renderer + 'a> ListBox<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
Renderer::Theme: StyleSheet + iced_style::rule::StyleSheet,
|
|
||||||
<<Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style: From<Container>,
|
|
||||||
<<Renderer as iced_native::Renderer>::Theme as iced_style::rule::StyleSheet>::Style:
|
|
||||||
From<theme::Rule>,
|
|
||||||
{
|
|
||||||
/// The default padding of a [`ListBox`] drawn by this renderer.
|
|
||||||
pub const DEFAULT_PADDING: u16 = 0;
|
|
||||||
|
|
||||||
/// Creates an empty [`ListBox`].
|
|
||||||
pub fn new() -> Self {
|
|
||||||
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>>) -> 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::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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'a, Renderer: iced_native::Renderer + 'a> std::default::Default
|
|
||||||
for ListBox<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
Renderer::Theme: StyleSheet + iced_style::rule::StyleSheet,
|
|
||||||
<<Renderer as iced_native::Renderer>::Theme as StyleSheet>::Style: From<Container>,
|
|
||||||
<<Renderer as iced_native::Renderer>::Theme as iced_style::rule::StyleSheet>::Style:
|
|
||||||
From<theme::Rule>,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Widget<Message, Renderer> for ListBox<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
Renderer: iced_native::Renderer,
|
|
||||||
<Renderer as iced_native::Renderer>::Theme: StyleSheet + iced_style::rule::StyleSheet,
|
|
||||||
{
|
|
||||||
fn width(&self) -> Length {
|
|
||||||
self.width
|
|
||||||
}
|
|
||||||
|
|
||||||
fn height(&self) -> Length {
|
|
||||||
self.height
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(&self, renderer: &Renderer, limits: &Limits) -> Node {
|
|
||||||
let limits = limits
|
|
||||||
.max_width(self.max_width)
|
|
||||||
.width(self.width)
|
|
||||||
.height(self.height);
|
|
||||||
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
resolve(
|
|
||||||
Axis::Vertical,
|
|
||||||
renderer,
|
|
||||||
&limits,
|
|
||||||
self.padding,
|
|
||||||
self.spacing as f32,
|
|
||||||
self.align_items,
|
|
||||||
&self.children,
|
|
||||||
)
|
|
||||||
} else if self.placeholder.is_some() {
|
|
||||||
self.placeholder
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.as_widget()
|
|
||||||
.layout(renderer, &limits)
|
|
||||||
} else {
|
|
||||||
Node::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(
|
|
||||||
&self,
|
|
||||||
state: &Tree,
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
theme: &Renderer::Theme,
|
|
||||||
style: &Style,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
) {
|
|
||||||
let color_scheme = theme.appearance(&self.style);
|
|
||||||
|
|
||||||
draw_background(renderer, &color_scheme, layout.bounds());
|
|
||||||
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
for ((child, state), layout) in self
|
|
||||||
.children
|
|
||||||
.iter()
|
|
||||||
.zip(&state.children)
|
|
||||||
.zip(layout.children())
|
|
||||||
{
|
|
||||||
child.as_widget().draw(
|
|
||||||
state,
|
|
||||||
renderer,
|
|
||||||
theme,
|
|
||||||
style,
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if let Some(placeholder) = &self.placeholder {
|
|
||||||
placeholder.as_widget().draw(
|
|
||||||
state,
|
|
||||||
renderer,
|
|
||||||
theme,
|
|
||||||
style,
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn children(&self) -> Vec<Tree> {
|
|
||||||
let widgets = if !self.children.is_empty() {
|
|
||||||
self.children.iter().map(Tree::new).collect()
|
|
||||||
} else if let Some(placeholder) = &self.placeholder {
|
|
||||||
vec![Tree::new(placeholder)]
|
|
||||||
} else {
|
|
||||||
vec![Tree::empty()]
|
|
||||||
};
|
|
||||||
widgets
|
|
||||||
}
|
|
||||||
|
|
||||||
fn diff(&self, tree: &mut Tree) {
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
tree.diff_children(&self.children);
|
|
||||||
} else if let Some(placeholder) = &self.placeholder {
|
|
||||||
tree.diff_children(&[placeholder]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operate(
|
|
||||||
&self,
|
|
||||||
state: &mut Tree,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
operation: &mut dyn Operation<Message>,
|
|
||||||
) {
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
operation.container(None, &mut |operation| {
|
|
||||||
self.children
|
|
||||||
.iter()
|
|
||||||
.zip(&mut state.children)
|
|
||||||
.zip(layout.children())
|
|
||||||
.for_each(|((child, state), layout)| {
|
|
||||||
child.as_widget().operate(state, layout, operation);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
} else if let Some(placeholder) = &self.placeholder {
|
|
||||||
placeholder.as_widget().operate(state, layout, operation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_event(
|
|
||||||
&mut self,
|
|
||||||
state: &mut Tree,
|
|
||||||
event: Event,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
renderer: &Renderer,
|
|
||||||
clipboard: &mut dyn Clipboard,
|
|
||||||
shell: &mut Shell<'_, Message>,
|
|
||||||
) -> Status {
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
self.children
|
|
||||||
.iter_mut()
|
|
||||||
.zip(&mut state.children)
|
|
||||||
.zip(layout.children())
|
|
||||||
.map(|((child, state), layout)| {
|
|
||||||
child.as_widget_mut().on_event(
|
|
||||||
state,
|
|
||||||
event.clone(),
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
renderer,
|
|
||||||
clipboard,
|
|
||||||
shell,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.fold(Status::Ignored, Status::merge)
|
|
||||||
} else if self.placeholder.is_some() {
|
|
||||||
self.placeholder.as_mut().unwrap().as_widget_mut().on_event(
|
|
||||||
&mut state.children[0],
|
|
||||||
event,
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
renderer,
|
|
||||||
clipboard,
|
|
||||||
shell,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Status::Ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mouse_interaction(
|
|
||||||
&self,
|
|
||||||
state: &Tree,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
cursor_position: Point,
|
|
||||||
viewport: &Rectangle,
|
|
||||||
renderer: &Renderer,
|
|
||||||
) -> Interaction {
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
self.children
|
|
||||||
.iter()
|
|
||||||
.zip(&state.children)
|
|
||||||
.zip(layout.children())
|
|
||||||
.map(|((child, state), layout)| {
|
|
||||||
child.as_widget().mouse_interaction(
|
|
||||||
state,
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
renderer,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.max()
|
|
||||||
.unwrap_or_default()
|
|
||||||
} else if let Some(placeholder) = &self.placeholder {
|
|
||||||
placeholder.as_widget().mouse_interaction(
|
|
||||||
&state.children[0],
|
|
||||||
layout,
|
|
||||||
cursor_position,
|
|
||||||
viewport,
|
|
||||||
renderer,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Interaction::Idle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overlay<'b>(
|
|
||||||
&'b self,
|
|
||||||
tree: &'b mut Tree,
|
|
||||||
layout: Layout<'_>,
|
|
||||||
renderer: &Renderer,
|
|
||||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
|
||||||
if !self.children.is_empty() {
|
|
||||||
from_children(&self.children, tree, layout, renderer)
|
|
||||||
} else if let Some(placeholder) = &self.placeholder {
|
|
||||||
placeholder
|
|
||||||
.as_widget()
|
|
||||||
.overlay(&mut tree.children[0], layout, renderer)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
|
|
||||||
pub fn draw_background<Renderer>(
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
appearance: &Appearance,
|
|
||||||
bounds: Rectangle,
|
|
||||||
) where
|
|
||||||
Renderer: iced_native::Renderer,
|
|
||||||
{
|
|
||||||
if appearance.background.is_some() || appearance.border_width > 0.0 {
|
|
||||||
renderer.fill_quad(
|
|
||||||
renderer::Quad {
|
|
||||||
bounds,
|
|
||||||
border_radius: appearance.border_radius,
|
|
||||||
border_width: appearance.border_width,
|
|
||||||
border_color: appearance.border_color,
|
|
||||||
},
|
|
||||||
appearance
|
|
||||||
.background
|
|
||||||
.unwrap_or(Background::Color(Color::TRANSPARENT)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Message: 'a, Renderer: iced_native::Renderer + 'a> From<ListBox<'a, Message, Renderer>>
|
|
||||||
for Element<'a, Message, Renderer>
|
|
||||||
where
|
|
||||||
<Renderer as iced_native::Renderer>::Theme: StyleSheet + iced_style::rule::StyleSheet,
|
|
||||||
{
|
|
||||||
fn from(list_box: ListBox<'a, Message, Renderer>) -> Self {
|
|
||||||
Self::new(list_box)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_box_item {
|
|
||||||
($($x:expr),+ $(,)?) => (
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
column(vec![
|
|
||||||
$($x),+
|
|
||||||
])
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
pub use list_box_item;
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_box_heading {
|
|
||||||
($title:expr) => {
|
|
||||||
$crate::iced::widget::container(
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
text($title).size(18),
|
|
||||||
$crate::iced::widget::vertical_space(Length::Fill),
|
|
||||||
$crate::iced::widget::horizontal_space(Length::Fill)
|
|
||||||
]
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_items($crate::iced::alignment::Alignment::Center),
|
|
||||||
)
|
|
||||||
.style($crate::iced::theme::Container::Custom(
|
|
||||||
$crate::widget::expander_heading_style,
|
|
||||||
))
|
|
||||||
.max_height(60)
|
|
||||||
.padding(10)
|
|
||||||
};
|
|
||||||
($title:expr, $subtitle:expr) => {
|
|
||||||
$crate::iced::widget::container(
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
column(vec![
|
|
||||||
text($title).size(18).into(),
|
|
||||||
text($subtitle).size(16).into(),
|
|
||||||
]),
|
|
||||||
$crate::iced::widget::vertical_space(Length::Fill),
|
|
||||||
$crate::iced::widget::horizontal_space(Length::Fill)
|
|
||||||
]
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_items($crate::iced::alignment::Alignment::Center),
|
|
||||||
)
|
|
||||||
.style($crate::iced::theme::Container::Custom(
|
|
||||||
$crate::widget::expander_heading_style,
|
|
||||||
))
|
|
||||||
.max_height(60)
|
|
||||||
.padding(10)
|
|
||||||
};
|
|
||||||
($title:expr, $subtitle:expr, $icon:expr) => {
|
|
||||||
$crate::iced::widget::container(
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
container($crate::widget::icon($icon, 20)).padding(10),
|
|
||||||
column(vec![
|
|
||||||
text($title).size(18).into(),
|
|
||||||
text($subtitle).size(16).into(),
|
|
||||||
]),
|
|
||||||
$crate::iced::widget::vertical_space(Length::Fill),
|
|
||||||
$crate::iced::widget::horizontal_space(Length::Fill)
|
|
||||||
]
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_items($crate::iced::alignment::Alignment::Center),
|
|
||||||
)
|
|
||||||
.style($crate::iced::theme::Container::Custom(
|
|
||||||
$crate::widget::expander_heading_style,
|
|
||||||
))
|
|
||||||
.max_height(60)
|
|
||||||
.padding(10)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub use list_box_heading;
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
use derive_setters::Setters;
|
|
||||||
|
|
||||||
#[derive(Setters, Default, Debug, Clone)]
|
|
||||||
pub struct ListRow<'a> {
|
|
||||||
pub(crate) title: &'a str,
|
|
||||||
#[setters(strip_option)]
|
|
||||||
pub subtitle: Option<&'a str>,
|
|
||||||
#[setters(strip_option)]
|
|
||||||
pub icon: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_row<'a>() -> ListRow<'a> {
|
|
||||||
ListRow {
|
|
||||||
title: "",
|
|
||||||
subtitle: None,
|
|
||||||
icon: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
pub use crate::Theme;
|
|
||||||
pub use iced::{widget, Background, Color};
|
|
||||||
|
|
||||||
pub mod list_view {
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_view {
|
|
||||||
($($x:expr),+ $(,)?) => (
|
|
||||||
$crate::iced::widget::Column::with_children(
|
|
||||||
vec![$($crate::iced::Element::from($x)),+]
|
|
||||||
)
|
|
||||||
.spacing(24)
|
|
||||||
.padding(24)
|
|
||||||
.max_width(600)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_view_row {
|
|
||||||
($($x:expr),+ $(,)?) => (
|
|
||||||
$crate::iced::widget::Row::with_children(vec![
|
|
||||||
$($crate::iced::Element::from($x)),+
|
|
||||||
])
|
|
||||||
.align_items(Alignment::Center)
|
|
||||||
.padding([0, 8])
|
|
||||||
.spacing(12)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_view_section {
|
|
||||||
($title:expr, $($x:expr),+ $(,)?) => (
|
|
||||||
$crate::iced::widget::Column::with_children(vec![
|
|
||||||
$crate::iced::widget::Text::new($title)
|
|
||||||
.font($crate::font::FONT_SEMIBOLD)
|
|
||||||
.into()
|
|
||||||
,
|
|
||||||
$crate::iced::widget::Container::new({
|
|
||||||
let mut children = vec![$($crate::iced::Element::from($x)),+];
|
|
||||||
|
|
||||||
//TODO: more efficient method for adding separators
|
|
||||||
let mut i = 1;
|
|
||||||
while i < children.len() {
|
|
||||||
children.insert(i, $crate::separator!(12).into());
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
$crate::iced::widget::Column::with_children(children)
|
|
||||||
.spacing(12)
|
|
||||||
})
|
|
||||||
.padding([12, 16])
|
|
||||||
.style(theme::Container::Custom(
|
|
||||||
list_section_style
|
|
||||||
))
|
|
||||||
.into()
|
|
||||||
])
|
|
||||||
.spacing(8)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_view_item {
|
|
||||||
($title:expr, $($x:expr),+ $(,)?) => (
|
|
||||||
$crate::list_view_row!(
|
|
||||||
$crate::iced::widget::Text::new($title),
|
|
||||||
$crate::iced::widget::horizontal_space(
|
|
||||||
$crate::iced::Length::Fill
|
|
||||||
),
|
|
||||||
$($x),+
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_section_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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::widget::{Background, Color};
|
|
||||||
use crate::Theme;
|
|
||||||
use iced::widget;
|
|
||||||
|
|
||||||
pub use list_view;
|
|
||||||
pub use list_view_item;
|
|
||||||
pub use list_view_row;
|
|
||||||
pub use list_view_section;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod list_box {
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! list_box_row {
|
|
||||||
($title:expr) => {
|
|
||||||
$crate::iced::widget::container(
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
text($title).size(18),
|
|
||||||
$crate::iced::widget::vertical_space(Length::Fill),
|
|
||||||
$crate::iced::widget::horizontal_space(Length::Fill)
|
|
||||||
]
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_items($crate::iced::alignment::Alignment::Center),
|
|
||||||
)
|
|
||||||
.max_height(60)
|
|
||||||
.padding(10)
|
|
||||||
};
|
|
||||||
($title:expr, $subtitle:expr) => {
|
|
||||||
$crate::iced::widget::container(
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
column(vec![
|
|
||||||
text($title).size(18).into(),
|
|
||||||
text($subtitle).size(16).into(),
|
|
||||||
]),
|
|
||||||
$crate::iced::widget::vertical_space(Length::Fill),
|
|
||||||
$crate::iced::widget::horizontal_space(Length::Fill)
|
|
||||||
]
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_items($crate::iced::alignment::Alignment::Center),
|
|
||||||
)
|
|
||||||
.max_height(60)
|
|
||||||
.padding(10)
|
|
||||||
};
|
|
||||||
($title:expr, $subtitle:expr, $icon:expr) => {
|
|
||||||
$crate::iced::widget::container(
|
|
||||||
$crate::iced::widget::row![
|
|
||||||
container($crate::widget::icon($icon, 20)).padding(10),
|
|
||||||
column(vec![
|
|
||||||
text($title).size(18).into(),
|
|
||||||
text($subtitle).size(16).into(),
|
|
||||||
]),
|
|
||||||
$crate::iced::widget::vertical_space(Length::Fill),
|
|
||||||
$crate::iced::widget::horizontal_space(Length::Fill)
|
|
||||||
]
|
|
||||||
.height(Length::Fill)
|
|
||||||
.align_items($crate::iced::alignment::Alignment::Center),
|
|
||||||
)
|
|
||||||
.max_height(60)
|
|
||||||
.padding(10)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use list_box_row;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
pub mod macros;
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
pub use macros::*;
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub mod list_row;
|
mod column;
|
||||||
pub use list_row::*;
|
// mod item;
|
||||||
|
|
||||||
pub mod list_box;
|
pub use self::column::{ListColumn, list_column};
|
||||||
pub use list_box::*;
|
// pub use self::item::{ListItem, list_item};
|
||||||
|
|
@ -1,29 +1,34 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
mod button;
|
mod button;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
|
|
||||||
mod header_bar;
|
mod header_bar;
|
||||||
pub use header_bar::*;
|
pub use header_bar::{HeaderBar, header_bar};
|
||||||
|
|
||||||
mod icon;
|
mod icon;
|
||||||
pub use self::icon::*;
|
pub use self::icon::{Icon, icon};
|
||||||
|
|
||||||
|
pub mod list;
|
||||||
|
pub use self::list::*;
|
||||||
|
|
||||||
|
pub mod nav_button;
|
||||||
|
pub use self::nav_button::{NavButton, nav_button};
|
||||||
|
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub use navigation::*;
|
pub use navigation::*;
|
||||||
|
|
||||||
mod toggler;
|
mod toggler;
|
||||||
pub use toggler::*;
|
pub use toggler::toggler;
|
||||||
|
|
||||||
|
pub mod settings;
|
||||||
|
|
||||||
mod scrollable;
|
mod scrollable;
|
||||||
pub use scrollable::*;
|
pub use scrollable::*;
|
||||||
|
|
||||||
mod expander;
|
|
||||||
pub use expander::*;
|
|
||||||
|
|
||||||
pub mod list;
|
|
||||||
pub use list::*;
|
|
||||||
|
|
||||||
pub mod separator;
|
pub mod separator;
|
||||||
pub use separator::*;
|
|
||||||
|
|
||||||
pub mod popup;
|
pub mod popup;
|
||||||
pub use popup::*;
|
pub use popup::*;
|
||||||
|
pub use separator::{horizontal_rule, vertical_rule};
|
||||||
|
|
|
||||||
61
src/widget/nav_button.rs
Normal file
61
src/widget/nav_button.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use apply::Apply;
|
||||||
|
use derive_setters::Setters;
|
||||||
|
use iced::{alignment::Vertical, Length};
|
||||||
|
use crate::{Element, theme};
|
||||||
|
|
||||||
|
#[derive(Setters)]
|
||||||
|
pub struct NavButton<'a, Message> {
|
||||||
|
title: &'a str,
|
||||||
|
sidebar_active: bool,
|
||||||
|
#[setters(strip_option)]
|
||||||
|
on_sidebar_toggled: Option<Message>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn nav_button<Message>(title: &str) -> NavButton<Message> {
|
||||||
|
NavButton {
|
||||||
|
title,
|
||||||
|
sidebar_active: false,
|
||||||
|
on_sidebar_toggled: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static + Clone> From<NavButton<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(nav_button: NavButton<'a, Message>) -> Self {
|
||||||
|
let text = iced::widget::text(&nav_button.title)
|
||||||
|
.style(theme::Text::Accent)
|
||||||
|
.vertical_alignment(Vertical::Center)
|
||||||
|
.width(Length::Shrink)
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
|
let icon = super::icon(
|
||||||
|
if nav_button.sidebar_active {
|
||||||
|
"go-previous-symbolic"
|
||||||
|
} else {
|
||||||
|
"go-next-symbolic"
|
||||||
|
},
|
||||||
|
24,
|
||||||
|
)
|
||||||
|
.style(theme::Svg::SymbolicActive)
|
||||||
|
.width(Length::Units(24))
|
||||||
|
.height(Length::Fill);
|
||||||
|
|
||||||
|
let mut widget = iced::widget::row!(text, crate::widget::vertical_rule(4), icon)
|
||||||
|
.padding(4)
|
||||||
|
.spacing(4)
|
||||||
|
.apply(iced::widget::button)
|
||||||
|
.style(theme::Button::Secondary);
|
||||||
|
|
||||||
|
if let Some(message) = nav_button.on_sidebar_toggled.clone() {
|
||||||
|
widget = widget.on_press(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
widget.apply(iced::widget::container)
|
||||||
|
.center_y()
|
||||||
|
.height(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub mod nav_bar {
|
pub mod nav_bar {
|
||||||
use crate::Theme;
|
use crate::Theme;
|
||||||
use iced::{widget, Background, Color};
|
use iced::{widget, Background, Color};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
pub mod navbar;
|
pub mod navbar;
|
||||||
pub use navbar::*;
|
pub use navbar::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
use crate::scrollable;
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style};
|
use crate::widget::nav_bar::{nav_bar_pages_style, nav_bar_sections_style};
|
||||||
use crate::widget::{icon, Background};
|
use crate::widget::{icon, scrollable};
|
||||||
use crate::{theme, Theme};
|
use crate::{theme, Renderer, Theme};
|
||||||
use derive_setters::Setters;
|
use derive_setters::Setters;
|
||||||
use iced::Length;
|
use iced::{Background, Length};
|
||||||
use iced_lazy::Component;
|
use iced_lazy::Component;
|
||||||
use iced_native::widget::{button, column, container, text};
|
use iced_native::widget::{button, column, container, text};
|
||||||
use iced_native::{row, Alignment, Element};
|
use iced_native::{row, Alignment, Element};
|
||||||
use iced_style::button::Appearance;
|
use iced_style::button::Appearance;
|
||||||
use iced_style::scrollable;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Setters, Default)]
|
#[derive(Setters, Default)]
|
||||||
|
|
@ -44,10 +45,7 @@ pub struct NavBarSection {
|
||||||
|
|
||||||
impl NavBarSection {
|
impl NavBarSection {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self::default()
|
||||||
title: String::new(),
|
|
||||||
icon: String::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -89,17 +87,7 @@ pub struct NavBarState {
|
||||||
page_active: bool,
|
page_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Component<Message, Renderer> for NavBar<'a, Message>
|
impl<'a, Message> Component<Message, Renderer> for NavBar<'a, Message> {
|
||||||
where
|
|
||||||
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>,
|
|
||||||
Renderer::Theme: iced_native::svg::StyleSheet,
|
|
||||||
{
|
|
||||||
type State = NavBarState;
|
type State = NavBarState;
|
||||||
type Event = NavBarEvent;
|
type Event = NavBarEvent;
|
||||||
|
|
||||||
|
|
@ -132,15 +120,15 @@ where
|
||||||
|
|
||||||
fn view(&self, state: &Self::State) -> Element<'a, Self::Event, Renderer> {
|
fn view(&self, state: &Self::State) -> Element<'a, Self::Event, Renderer> {
|
||||||
if self.active {
|
if self.active {
|
||||||
let mut sections: Vec<Element<Self::Event, Renderer>> = vec![];
|
let mut sections: Vec<Element<'a, Self::Event, Renderer>> = vec![];
|
||||||
let mut pages: Vec<Element<Self::Event, Renderer>> = vec![];
|
let mut pages: Vec<Element<'a, Self::Event, Renderer>> = vec![];
|
||||||
|
|
||||||
for (section, section_pages) in &self.source {
|
for (section, section_pages) in &self.source {
|
||||||
sections.push(
|
sections.push(
|
||||||
button(
|
button(
|
||||||
column(vec![
|
column(vec![
|
||||||
icon(§ion.icon, 20).into(),
|
icon(section.icon.clone(), 20).into(),
|
||||||
text(§ion.title).size(14).into(),
|
text(section.title.clone()).size(14).into(),
|
||||||
])
|
])
|
||||||
.width(Length::Units(100))
|
.width(Length::Units(100))
|
||||||
.height(Length::Units(50))
|
.height(Length::Units(50))
|
||||||
|
|
@ -179,16 +167,16 @@ where
|
||||||
|
|
||||||
let nav_bar: Element<Self::Event, Renderer> =
|
let nav_bar: Element<Self::Event, Renderer> =
|
||||||
container(if self.condensed && state.selected_page.is_some() {
|
container(if self.condensed && state.selected_page.is_some() {
|
||||||
row![container(scrollable!(column(pages)
|
row![container(scrollable(column(pages)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.max_width(200)
|
.max_width(200)
|
||||||
.width(Length::Units(200))
|
.width(Length::Units(200))
|
||||||
.height(Length::Shrink)))
|
.height(Length::Shrink)))
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.style(theme::Container::Custom(nav_bar_pages_style))]
|
.style(theme::Container::Custom(nav_bar_pages_style))]
|
||||||
} else if !state.section_active || self.condensed && state.selected_page.is_none() {
|
} else if !state.section_active || self.condensed && state.selected_page.is_none() {
|
||||||
row![scrollable!(column(sections)
|
row![scrollable(column(sections)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.max_width(100)
|
.max_width(100)
|
||||||
|
|
@ -196,13 +184,13 @@ where
|
||||||
.height(Length::Shrink))]
|
.height(Length::Shrink))]
|
||||||
} else {
|
} else {
|
||||||
row![
|
row![
|
||||||
scrollable!(column(sections)
|
scrollable(column(sections)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.max_width(100)
|
.max_width(100)
|
||||||
.align_items(Alignment::Center)
|
.align_items(Alignment::Center)
|
||||||
.height(Length::Shrink)),
|
.height(Length::Shrink)),
|
||||||
container(scrollable!(column(pages)
|
container(scrollable(column(pages)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.max_width(200)
|
.max_width(200)
|
||||||
|
|
@ -222,16 +210,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message: 'a, Renderer> From<NavBar<'a, Message>> for Element<'a, Message, Renderer>
|
impl<'a, Message: 'static> From<NavBar<'a, Message>>
|
||||||
where
|
for Element<'a, Message, Renderer>
|
||||||
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>,
|
|
||||||
Renderer::Theme: iced_native::svg::StyleSheet,
|
|
||||||
{
|
{
|
||||||
fn from(nav_bar: NavBar<'a, Message>) -> Self {
|
fn from(nav_bar: NavBar<'a, Message>) -> Self {
|
||||||
iced_lazy::component(nav_bar)
|
iced_lazy::component(nav_bar)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
#[macro_export]
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
macro_rules! scrollable {
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
($x:expr) => {
|
|
||||||
$crate::iced::widget::scrollable($x)
|
use crate::{Element, Renderer};
|
||||||
.scrollbar_width(8)
|
use iced::widget;
|
||||||
.scroller_width(8)
|
|
||||||
};
|
pub fn scrollable<'a, Message>(element: impl Into<Element<'a, Message>>) -> widget::Scrollable<'a, Message, Renderer> {
|
||||||
}
|
widget::scrollable(element)
|
||||||
|
.scrollbar_width(8)
|
||||||
|
.scroller_width(8)
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,25 @@
|
||||||
#[macro_export]
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
macro_rules! separator {
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
($size:expr) => {
|
|
||||||
$crate::iced::widget::horizontal_rule($size)
|
use crate::iced::widget;
|
||||||
.style($crate::theme::Rule::Custom($crate::widget::separator_style))
|
use crate::{theme, Renderer, Theme};
|
||||||
};
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn horizontal_rule(size: u16) -> widget::Rule<Renderer> {
|
||||||
|
widget::horizontal_rule(size).style(theme::Rule::Custom(separator_style))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn vertical_rule(size: u16) -> widget::Rule<Renderer> {
|
||||||
|
widget::vertical_rule(size).style(theme::Rule::Custom(separator_style))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn separator_style(theme: &Theme) -> widget::rule::Appearance {
|
||||||
|
let cosmic = &theme.cosmic().primary;
|
||||||
|
widget::rule::Appearance {
|
||||||
|
color: cosmic.divider.into(),
|
||||||
|
width: 1,
|
||||||
|
radius: 0.0,
|
||||||
|
fill_mode: widget::rule::FillMode::Padded(10),
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/widget/settings/item.rs
Normal file
26
src/widget/settings/item.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::{Element, Renderer};
|
||||||
|
|
||||||
|
/// A setting within a settings view section.
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub fn item<'a, Message: 'static>(title: &'a str, widget: impl Into<Element<'a, Message>>) -> iced::widget::Row<'a, Message, Renderer> {
|
||||||
|
item_row(vec![
|
||||||
|
iced::widget::text(title).into(),
|
||||||
|
iced::widget::horizontal_space(iced::Length::Fill).into(),
|
||||||
|
widget.into()
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A settings item aligned in a row
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub fn item_row<Message>(children: Vec<Element<Message>>) -> iced::widget::Row<Message, Renderer> {
|
||||||
|
iced::widget::row(children)
|
||||||
|
.align_items(iced::Alignment::Center)
|
||||||
|
.padding([0, 8])
|
||||||
|
.spacing(12)
|
||||||
|
}
|
||||||
|
|
||||||
17
src/widget/settings/mod.rs
Normal file
17
src/widget/settings/mod.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
mod item;
|
||||||
|
mod section;
|
||||||
|
|
||||||
|
pub use self::item::{item, item_row};
|
||||||
|
pub use self::section::{Section, view_section};
|
||||||
|
|
||||||
|
use crate::{Element, Renderer};
|
||||||
|
use iced::widget::{Column, column};
|
||||||
|
|
||||||
|
/// A column with a predefined style for creating a settings panel
|
||||||
|
#[must_use]
|
||||||
|
pub fn view_column<Message: 'static>(children: Vec<Element<Message>>) -> Column<Message, Renderer> {
|
||||||
|
column(children).spacing(24).padding(24).max_width(600)
|
||||||
|
}
|
||||||
39
src/widget/settings/section.rs
Normal file
39
src/widget/settings/section.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::{Element};
|
||||||
|
use crate::widget::{ListColumn};
|
||||||
|
use iced::widget::{column, text};
|
||||||
|
|
||||||
|
/// A section within a settings view column.
|
||||||
|
#[must_use]
|
||||||
|
pub fn view_section<Message: 'static>(
|
||||||
|
title: &str,
|
||||||
|
) -> Section<Message> {
|
||||||
|
Section { title, children: ListColumn::default() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Section<'a, Message> {
|
||||||
|
title: &'a str,
|
||||||
|
children: ListColumn<'a, Message>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> Section<'a, Message> {
|
||||||
|
#[must_use]
|
||||||
|
pub fn add(mut self, item: impl Into<Element<'a, Message>>) -> Self {
|
||||||
|
self.children = self.children.add(item.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Message: 'static> From<Section<'a, Message>> for Element<'a, Message> {
|
||||||
|
fn from(data: Section<'a, Message>) -> Self {
|
||||||
|
let title = text(data.title)
|
||||||
|
.font(crate::font::FONT_SEMIBOLD)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
column(vec![title, data.children.into_element()])
|
||||||
|
.spacing(8)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
|
// Copyright 2022 System76 <info@system76.com>
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::Renderer;
|
||||||
use iced::{widget, Length};
|
use iced::{widget, Length};
|
||||||
|
|
||||||
pub fn toggler<'a, Message, Renderer>(
|
pub fn toggler<'a, Message>(
|
||||||
label: impl Into<Option<String>>,
|
label: impl Into<Option<String>>,
|
||||||
is_checked: bool,
|
is_checked: bool,
|
||||||
f: impl Fn(bool) -> Message + 'a,
|
f: impl Fn(bool) -> Message + 'a,
|
||||||
) -> widget::Toggler<'a, Message, Renderer>
|
) -> widget::Toggler<'a, Message, Renderer> {
|
||||||
where
|
|
||||||
Renderer: iced_native::text::Renderer,
|
|
||||||
Renderer::Theme: widget::toggler::StyleSheet,
|
|
||||||
{
|
|
||||||
widget::Toggler::new(is_checked, label, f)
|
widget::Toggler::new(is_checked, label, f)
|
||||||
.size(24)
|
.size(24)
|
||||||
.spacing(12)
|
.spacing(12)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue