diff --git a/Cargo.toml b/Cargo.toml index 76dedc2..a26b3e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,10 @@ edition = "2021" name = "cosmic" [features] -default = [] +default = ["wgpu"] debug = ["iced/debug"] +wayland = ["iced/wayland"] +wgpu = ["iced/wgpu"] [dependencies] freedesktop-icons = "0.2.1" @@ -24,6 +26,7 @@ git = "https://github.com/pop-os/cosmic-theme.git" git = "https://github.com/pop-os/iced.git" branch = "sctk-cosmic" # path = "../iced" +default-features = false features = ["image", "svg"] [dependencies.iced_core] diff --git a/examples/cosmic-sctk/Cargo.toml b/examples/cosmic-sctk/Cargo.toml new file mode 100644 index 0000000..a599be5 --- /dev/null +++ b/examples/cosmic-sctk/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "cosmic_sctk" +version = "0.1.0" +authors = [] +edition = "2021" +publish = false + +[dependencies] +libcosmic = { path = "../..", default-features = false, features = ["debug", "wayland"] } diff --git a/examples/cosmic-sctk/README.md b/examples/cosmic-sctk/README.md new file mode 100644 index 0000000..c52803b --- /dev/null +++ b/examples/cosmic-sctk/README.md @@ -0,0 +1,9 @@ +# COSMIC +An example of the COSMIC design system. + +All the example code is located in the __[`main`](src/main.rs)__ file. + +You can run it with `cargo run`: +``` +cargo run --package cosmic --release +``` diff --git a/examples/cosmic-sctk/src/main.rs b/examples/cosmic-sctk/src/main.rs new file mode 100644 index 0000000..2fba913 --- /dev/null +++ b/examples/cosmic-sctk/src/main.rs @@ -0,0 +1,10 @@ +use cosmic::{iced::{Application, sctk_settings::InitialSurface}, settings}; + +mod window; +pub use window::*; + +pub fn main() -> cosmic::iced::Result { + let mut settings = settings(); + settings.initial_surface = InitialSurface::XdgWindow(Default::default()); + Window::run(settings) +} diff --git a/examples/cosmic-sctk/src/window.rs b/examples/cosmic-sctk/src/window.rs new file mode 100644 index 0000000..de07e44 --- /dev/null +++ b/examples/cosmic-sctk/src/window.rs @@ -0,0 +1,377 @@ +use cosmic::widget::{expander, nav_bar, nav_bar_page, nav_bar_section}; +use cosmic::{ + iced::widget::{ + checkbox, column, container, horizontal_space, pick_list, progress_bar, radio, row, slider, + text, + }, + iced::{self, Alignment, Application, Color, Command, Length}, + iced_lazy::responsive, + iced_winit::window::{drag, maximize, minimize}, + iced_native::window, + list_view, list_view_item, list_view_row, list_view_section, scrollable, + theme::{self, Theme}, + widget::{button, header_bar, list_box, list_row, list_view::*, toggler}, + Element, +}; +use std::collections::BTreeMap; + + +#[derive(Default)] +pub struct Window { + page: u8, + debug: bool, + theme: Theme, + slider_value: f32, + checkbox_value: bool, + toggler_value: bool, + pick_list_selected: Option<&'static str>, + sidebar_toggled: bool, + show_minimize: bool, + show_maximize: bool, + exit: bool, +} + +impl Window { + pub fn sidebar_toggled(mut self, toggled: bool) -> Self { + self.sidebar_toggled = toggled; + self + } + + pub fn show_maximize(mut self, show: bool) -> Self { + self.show_maximize = show; + self + } + + pub fn show_minimize(mut self, show: bool) -> Self { + self.show_minimize = show; + self + } +} + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +pub enum Message { + Page(u8), + Debug(bool), + ThemeChanged(Theme), + ButtonPressed, + SliderChanged(f32), + CheckboxToggled(bool), + TogglerToggled(bool), + PickListSelected(&'static str), + RowSelected(usize), + Close, + ToggleSidebar, + Drag, + Minimize, + Maximize, +} + +impl Application for Window { + type Executor = iced::executor::Default; + type Flags = (); + type Message = Message; + type Theme = Theme; + + fn new(_flags: ()) -> (Self, Command) { + let mut window = Window::default() + .sidebar_toggled(true) + .show_maximize(true) + .show_minimize(true); + window.slider_value = 50.0; + // window.theme = Theme::Light; + window.pick_list_selected = Some("Option 1"); + (window, Command::none()) + } + + fn title(&self) -> String { + String::from("COSMIC Design System - Iced") + } + + fn update(&mut self, message: Message) -> iced::Command { + match message { + Message::Page(page) => self.page = page, + Message::Debug(debug) => self.debug = debug, + Message::ThemeChanged(theme) => self.theme = theme, + Message::ButtonPressed => {} + Message::SliderChanged(value) => self.slider_value = value, + Message::CheckboxToggled(value) => self.checkbox_value = value, + Message::TogglerToggled(value) => self.toggler_value = value, + Message::PickListSelected(value) => self.pick_list_selected = Some(value), + Message::Close => self.exit = true, + Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled, + Message::Drag => return drag(window::Id::new(0)), + Message::Minimize => return minimize(window::Id::new(0), true), + Message::Maximize => return maximize(window::Id::new(0), true), + Message::RowSelected(row) => println!("Selected row {row}"), + } + + Command::none() + } + fn view_popup(&self, id: window::Id) -> Element { + unimplemented!() + } + fn view_layer_surface( + &self, + window: cosmic::iced_native::window::Id, + ) -> iced::Element<'_, Self::Message, iced::Renderer> { + unimplemented!() + } + fn close_window_requested(&self, window: cosmic::iced_native::window::Id) -> Self::Message { + unimplemented!() + } + fn popup_done(&self, window: cosmic::iced_native::window::Id) -> Self::Message { + unimplemented!() + } + fn layer_surface_done(&self, window: cosmic::iced_native::window::Id) -> Self::Message { + unimplemented!() + } + + fn view_window(&self, id: window::Id) -> Element { + let mut header: Element = header_bar() + .title(self.title()) + .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_drag(Message::Drag) + .on_maximize(Message::Maximize) + .on_minimize(Message::Minimize) + .on_sidebar_toggle(Message::ToggleSidebar) + .into(); + + if self.debug { + header = header.explain(Color::WHITE); + } + + // 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 + // responsive and leave the content to be sized normally. + let content = responsive(|size| { + let condensed = size.width < 900.0; + + // cosmic::navbar![ + // nav_button!("network-wireless", "Network & Wireless", condensed) + // .on_press(Message::Page(0)) + // .style(if self.page == 0 { + // theme::Button::Primary + // } else { + // theme::Button::Text + // }), + // nav_button!("preferences-desktop", "Bluetooth", condensed) + // .on_press(Message::Page(1)) + // .style(if self.page == 1 { + // theme::Button::Primary + // } else { + // theme::Button::Text + // }), + // nav_button!("system-software-update", "Personalization", condensed) + // .on_press(Message::Page(2)) + // .style(if self.page == 2 { + // theme::Button::Primary + // } else { + // theme::Button::Text + // }), + // ] + + let sidebar: Element<_> = nav_bar() + .source(BTreeMap::from([ + ( + nav_bar_section() + .title("Network & Wireless") + .icon("network-wireless"), + vec![nav_bar_page("Wi-Fi")], + ), + ( + nav_bar_section() + .title("Bluetooth") + .icon("cs-bluetooth"), + vec![nav_bar_page("Devices")], + ), + ( + nav_bar_section() + .title("Personalization") + .icon("applications-system"), + vec![ + nav_bar_page("Desktop Session"), + nav_bar_page("Wallpaper"), + nav_bar_page("Appearance"), + nav_bar_page("Dock & Top Panel"), + nav_bar_page("Workspaces"), + nav_bar_page("Notifications"), + ], + ), + ( + nav_bar_section() + .title("Input Devices") + .icon("input-keyboard"), + vec![nav_bar_page("Keyboard")], + ), + ( + nav_bar_section().title("Displays").icon("cs-display"), + vec![nav_bar_page("Keyboard")], + ), + ( + nav_bar_section() + .title("Power & Battery") + .icon("battery"), + vec![nav_bar_page("Status")], + ), + ( + nav_bar_section().title("Sound").icon("sound"), + vec![nav_bar_page("Volume")], + ), + ])) + .active(self.sidebar_toggled) + .condensed(condensed) + .into(); + + let choose_theme = [Theme::Light, Theme::Dark].iter().fold( + row![].spacing(10).align_items(Alignment::Center), + |row, theme| { + row.push(radio( + format!("{:?}", theme), + *theme, + Some(self.theme), + Message::ThemeChanged, + )) + }, + ); + + let content: Element<_> = list_view!( + list_view_section!( + "Debug", + list_view_item!("Debug theme", choose_theme), + list_view_item!( + "Debug layout", + toggler(String::from("Debug layout"), self.debug, Message::Debug,) + ) + ), + list_view_section!( + "Buttons", + list_view_row!( + button!("Primary") + .style(theme::Button::Primary) + .on_press(Message::ButtonPressed), + button!("Secondary") + .style(theme::Button::Secondary) + .on_press(Message::ButtonPressed), + button!("Positive") + .style(theme::Button::Positive) + .on_press(Message::ButtonPressed), + button!("Destructive") + .style(theme::Button::Destructive) + .on_press(Message::ButtonPressed), + button!("Text") + .style(theme::Button::Text) + .on_press(Message::ButtonPressed), + ), + list_view_row!( + button!("Primary").style(theme::Button::Primary), + button!("Secondary").style(theme::Button::Secondary), + button!("Positive").style(theme::Button::Positive), + button!("Destructive").style(theme::Button::Destructive), + button!("Text").style(theme::Button::Text), + ), + ), + list_view_section!( + "Controls", + list_view_item!( + "Toggler", + toggler(None, self.toggler_value, Message::TogglerToggled) + ), + list_view_item!( + "Pick List (TODO)", + pick_list( + vec!["Option 1", "Option 2", "Option 3", "Option 4",], + self.pick_list_selected, + Message::PickListSelected + ) + .padding([8, 0, 8, 16]) + ), + list_view_item!( + "Slider", + slider(0.0..=100.0, self.slider_value, Message::SliderChanged) + .width(Length::Units(250)) + ), + list_view_item!( + "Progress", + progress_bar(0.0..=100.0, self.slider_value) + .width(Length::Units(250)) + .height(Length::Units(4)) + ), + checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled), + ), + 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(); + + let mut widgets = Vec::with_capacity(2); + + widgets.push(if self.debug { + sidebar.explain(Color::WHITE) + } else { + sidebar + }); + + widgets.push( + scrollable!(row![ + horizontal_space(Length::Fill), + if self.debug { + content.explain(Color::WHITE) + } else { + content + }, + horizontal_space(Length::Fill), + ]) + .into(), + ); + + container(row(widgets)) + .padding([16, 16]) + .width(Length::Fill) + .height(Length::Fill) + .into() + }) + .into(); + + column(vec![header, content]).into() + } + + fn should_exit(&self) -> bool { + self.exit + } + + fn theme(&self) -> Theme { + self.theme + } +} diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index b73d5e1..28ecfdc 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -100,8 +100,8 @@ impl Application for Window { Message::Close => self.exit = true, Message::ToggleSidebar => self.sidebar_toggled = !self.sidebar_toggled, Message::Drag => return drag(window::Id::new(0)), - Message::Minimize => return minimize(window::Id::new(0)), - Message::Maximize => return maximize(window::Id::new(0)), + Message::Minimize => return minimize(window::Id::new(0), true), + Message::Maximize => return maximize(window::Id::new(0), true), Message::RowSelected(row) => println!("Selected row {row}"), } diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 2775f3a..b3f921b 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -82,7 +82,7 @@ impl Default for Application { impl application::StyleSheet for Theme { type Style = Application; - fn appearance(&self, style: Self::Style) -> application::Appearance { + fn appearance(&self, style: &Self::Style) -> application::Appearance { let cosmic = self.cosmic(); match style { @@ -129,7 +129,7 @@ impl Button { impl button::StyleSheet for Theme { type Style = Button; - fn active(&self, style: Self::Style) -> button::Appearance { + fn active(&self, style: &Self::Style) -> button::Appearance { let cosmic = style.cosmic(self); button::Appearance { @@ -143,8 +143,8 @@ impl button::StyleSheet for Theme { } } - fn hovered(&self, style: Self::Style) -> button::Appearance { - let active = self.active(style); + fn hovered(&self, style: &Self::Style) -> button::Appearance { + let active = self.active(&style); let cosmic = style.cosmic(self); button::Appearance { @@ -176,7 +176,7 @@ impl checkbox::StyleSheet for Theme { fn active( &self, - style: Self::Style, + style: &Self::Style, is_checked: bool, ) -> checkbox::Appearance { let palette = self.extended_palette(); @@ -211,7 +211,7 @@ impl checkbox::StyleSheet for Theme { fn hovered( &self, - style: Self::Style, + style: &Self::Style, is_checked: bool, ) -> checkbox::Appearance { let palette = self.extended_palette(); @@ -319,7 +319,7 @@ impl From container::Appearance> for Container { impl container::StyleSheet for Theme { type Style = Container; - fn appearance(&self, style: Self::Style) -> container::Appearance { + fn appearance(&self, style: &Self::Style) -> container::Appearance { match style { Container::Transparent => Default::default(), Container::Box => { @@ -344,7 +344,7 @@ impl container::StyleSheet for Theme { impl slider::StyleSheet for Theme { type Style = (); - fn active(&self, _style: Self::Style) -> slider::Appearance { + fn active(&self, _style: &Self::Style) -> slider::Appearance { let cosmic = self.cosmic(); //TODO: no way to set rail thickness @@ -365,8 +365,8 @@ impl slider::StyleSheet for Theme { } } - fn hovered(&self, style: Self::Style) -> slider::Appearance { - let mut style = self.active(style); + fn hovered(&self, style: &Self::Style) -> slider::Appearance { + let mut style = self.active(&style); style.handle.shape = slider::HandleShape::Circle { radius: 16.0 }; @@ -378,8 +378,8 @@ impl slider::StyleSheet for Theme { style } - fn dragging(&self, style: Self::Style) -> slider::Appearance { - let mut style = self.hovered(style); + fn dragging(&self, style: &Self::Style) -> slider::Appearance { + let mut style = self.hovered(&style); style.handle.border_color = match self { Theme::Dark => Color::from_rgba8(0xFF, 0xFF, 0xFF, 0.2), Theme::Light => Color::from_rgba8(0, 0, 0, 0.2), @@ -394,7 +394,7 @@ impl slider::StyleSheet for Theme { impl menu::StyleSheet for Theme { type Style = (); - fn appearance(&self, _style: Self::Style) -> menu::Appearance { + fn appearance(&self, _style: &Self::Style) -> menu::Appearance { let cosmic = self.cosmic(); menu::Appearance { @@ -415,7 +415,7 @@ impl menu::StyleSheet for Theme { impl pick_list::StyleSheet for Theme { type Style = (); - fn active(&self, _style: ()) -> pick_list::Appearance { + fn active(&self, _style: &()) -> pick_list::Appearance { let cosmic = &self.cosmic().primary.component; pick_list::Appearance { @@ -429,12 +429,12 @@ impl pick_list::StyleSheet for Theme { } } - fn hovered(&self, style: ()) -> pick_list::Appearance { + fn hovered(&self, style: &()) -> pick_list::Appearance { let cosmic = &self.cosmic().primary.component; pick_list::Appearance { background: Background::Color(cosmic.hover.into()), - ..self.active(style) + ..self.active(&style) } } } @@ -445,7 +445,7 @@ impl pick_list::StyleSheet for Theme { impl radio::StyleSheet for Theme { type Style = (); - fn active(&self, _style: Self::Style) -> radio::Appearance { + fn active(&self, _style: &Self::Style, is_checked: bool) -> radio::Appearance { let palette = self.extended_palette(); radio::Appearance { @@ -457,8 +457,8 @@ impl radio::StyleSheet for Theme { } } - fn hovered(&self, style: Self::Style) -> radio::Appearance { - let active = self.active(style); + fn hovered(&self, style: &Self::Style, is_checked: bool) -> radio::Appearance { + let active = self.active(&style, is_checked); let palette = self.extended_palette(); radio::Appearance { @@ -477,7 +477,7 @@ impl toggler::StyleSheet for Theme { fn active( &self, - _style: Self::Style, + _style: &Self::Style, is_active: bool, ) -> toggler::Appearance { let palette = self.palette(); @@ -504,7 +504,7 @@ impl toggler::StyleSheet for Theme { fn hovered( &self, - style: Self::Style, + style: &Self::Style, is_active: bool, ) -> toggler::Appearance { //TODO: grab colors from palette @@ -515,7 +515,7 @@ impl toggler::StyleSheet for Theme { } else { Color::from_rgb8(0xb6, 0xb6, 0xb6) }, - ..self.active(style, is_active) + ..self.active(&style, is_active) }, Theme::Light => toggler::Appearance { background: if is_active { @@ -523,7 +523,7 @@ impl toggler::StyleSheet for Theme { } else { Color::from_rgb8(0x54, 0x54, 0x54) }, - ..self.active(style, is_active) + ..self.active(&style, is_active) } } } @@ -535,7 +535,7 @@ impl toggler::StyleSheet for Theme { impl pane_grid::StyleSheet for Theme { type Style = (); - fn picked_split(&self, _style: Self::Style) -> Option { + fn picked_split(&self, _style: &Self::Style) -> Option { let palette = self.extended_palette(); Some(pane_grid::Line { @@ -544,7 +544,7 @@ impl pane_grid::StyleSheet for Theme { }) } - fn hovered_split(&self, _style: Self::Style) -> Option { + fn hovered_split(&self, _style: &Self::Style) -> Option { let palette = self.extended_palette(); Some(pane_grid::Line { @@ -574,7 +574,7 @@ impl Default for ProgressBar { impl progress_bar::StyleSheet for Theme { type Style = ProgressBar; - fn appearance(&self, style: Self::Style) -> progress_bar::Appearance { + fn appearance(&self, style: &Self::Style) -> progress_bar::Appearance { let palette = self.extended_palette(); let from_palette = |bar: Color| progress_bar::Appearance { @@ -610,7 +610,7 @@ impl Default for Rule { impl rule::StyleSheet for Theme { type Style = Rule; - fn style(&self, style: Self::Style) -> rule::Appearance { + fn appearance(&self, style: &Self::Style) -> rule::Appearance { let palette = self.extended_palette(); match style { @@ -631,7 +631,7 @@ impl rule::StyleSheet for Theme { impl scrollable::StyleSheet for Theme { type Style = (); - fn active(&self, _style: Self::Style) -> scrollable::Scrollbar { + fn active(&self, _style: &Self::Style) -> scrollable::Scrollbar { let palette = self.extended_palette(); scrollable::Scrollbar { @@ -648,7 +648,7 @@ impl scrollable::StyleSheet for Theme { } } - fn hovered(&self, _style: Self::Style) -> scrollable::Scrollbar { + fn hovered(&self, _style: &Self::Style) -> scrollable::Scrollbar { let palette = self.extended_palette(); scrollable::Scrollbar { @@ -730,7 +730,7 @@ impl text::StyleSheet for Theme { impl text_input::StyleSheet for Theme { type Style = (); - fn active(&self, _style: Self::Style) -> text_input::Appearance { + fn active(&self, _style: &Self::Style) -> text_input::Appearance { let palette = self.extended_palette(); text_input::Appearance { @@ -741,7 +741,7 @@ impl text_input::StyleSheet for Theme { } } - fn hovered(&self, _style: Self::Style) -> text_input::Appearance { + fn hovered(&self, _style: &Self::Style) -> text_input::Appearance { let palette = self.extended_palette(); text_input::Appearance { @@ -752,7 +752,7 @@ impl text_input::StyleSheet for Theme { } } - fn focused(&self, _style: Self::Style) -> text_input::Appearance { + fn focused(&self, _style: &Self::Style) -> text_input::Appearance { let palette = self.extended_palette(); text_input::Appearance { @@ -763,19 +763,19 @@ impl text_input::StyleSheet for Theme { } } - fn placeholder_color(&self, _style: Self::Style) -> Color { + fn placeholder_color(&self, _style: &Self::Style) -> Color { let palette = self.extended_palette(); palette.background.strong.color } - fn value_color(&self, _style: Self::Style) -> Color { + fn value_color(&self, _style: &Self::Style) -> Color { let palette = self.extended_palette(); palette.background.base.text } - fn selection_color(&self, _style: Self::Style) -> Color { + fn selection_color(&self, _style: &Self::Style) -> Color { let palette = self.extended_palette(); palette.primary.weak.color diff --git a/src/widget/list/list_box.rs b/src/widget/list/list_box.rs index d689a7b..212d6dd 100644 --- a/src/widget/list/list_box.rs +++ b/src/widget/list/list_box.rs @@ -175,7 +175,7 @@ where cursor_position: Point, viewport: &Rectangle, ) { - let color_scheme = theme.appearance(self.style); + let color_scheme = theme.appearance(&self.style); draw_background(renderer, &color_scheme, layout.bounds());