From 37d5dd8b65069f539e7a1ff4c21f0e74df59eb6a Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 13 Sep 2023 15:18:20 +0200 Subject: [PATCH] feat(examples): improve design demo and merge cosmic-sctk example --- examples/cosmic-sctk/Cargo.toml | 11 - examples/cosmic-sctk/README.md | 3 - examples/cosmic-sctk/src/main.rs | 11 - examples/cosmic-sctk/src/window.rs | 536 ------------------ examples/design-demo/Cargo.toml | 21 + .../com.system76.CosmicDesignDemo.desktop | 10 + examples/design-demo/src/buttons.rs | 352 ++++++++++++ examples/design-demo/src/cards.rs | 52 ++ examples/design-demo/src/debug.rs | 138 +++++ examples/design-demo/src/inputs.rs | 193 +++++++ examples/design-demo/src/main.rs | 356 ++++++++++++ examples/design-demo/src/typography.rs | 106 ++++ examples/design/Cargo.toml | 13 - examples/design/src/main.rs | 325 ----------- justfile | 2 +- 15 files changed, 1229 insertions(+), 900 deletions(-) delete mode 100644 examples/cosmic-sctk/Cargo.toml delete mode 100644 examples/cosmic-sctk/README.md delete mode 100644 examples/cosmic-sctk/src/main.rs delete mode 100644 examples/cosmic-sctk/src/window.rs create mode 100644 examples/design-demo/Cargo.toml create mode 100644 examples/design-demo/resources/com.system76.CosmicDesignDemo.desktop create mode 100644 examples/design-demo/src/buttons.rs create mode 100644 examples/design-demo/src/cards.rs create mode 100644 examples/design-demo/src/debug.rs create mode 100644 examples/design-demo/src/inputs.rs create mode 100644 examples/design-demo/src/main.rs create mode 100644 examples/design-demo/src/typography.rs delete mode 100644 examples/design/Cargo.toml delete mode 100644 examples/design/src/main.rs diff --git a/examples/cosmic-sctk/Cargo.toml b/examples/cosmic-sctk/Cargo.toml deleted file mode 100644 index 81f82ca9..00000000 --- a/examples/cosmic-sctk/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "cosmic_sctk" -version = "0.1.0" -authors = [] -edition = "2021" -publish = false - -[dependencies] -libcosmic = { path = "../..", features = ["wayland", "tokio"] } -cosmic-time = { git = "https://github.com/pop-os/cosmic-time", branch = "icon-color", default-features = false, features = ["libcosmic", "once_cell"] } -# cosmic-time = { path = "../../../cosmic-time", default-features = false, features = ["libcosmic", "once_cell"]} \ No newline at end of file diff --git a/examples/cosmic-sctk/README.md b/examples/cosmic-sctk/README.md deleted file mode 100644 index 04483068..00000000 --- a/examples/cosmic-sctk/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Deprecated - -This example will be removed once its contents are migrated to the design demo. \ No newline at end of file diff --git a/examples/cosmic-sctk/src/main.rs b/examples/cosmic-sctk/src/main.rs deleted file mode 100644 index 7ce3ddcb..00000000 --- a/examples/cosmic-sctk/src/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmic::iced::{wayland::InitialSurface, Application, Settings}; - -mod window; -pub use window::Window; - -pub fn main() -> cosmic::iced::Result { - cosmic::icon_theme::set_default("Pop"); - let mut settings = Settings::default(); - 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 deleted file mode 100644 index 8156ec61..00000000 --- a/examples/cosmic-sctk/src/window.rs +++ /dev/null @@ -1,536 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use cosmic::{ - iced::{self, wayland::window::set_mode_window, Application, Command, Length}, - iced::{ - wayland::window::{start_drag_window, toggle_maximize}, - widget::{column, container, horizontal_space, pick_list, progress_bar, row, slider}, - window, Color, - }, - iced_futures::Subscription, - iced_style::application, - prelude::*, - theme::{self, Theme}, - widget::{ - button, cosmic_container, header_bar, icon, inline_input, nav_bar, nav_bar_toggle, - rectangle_tracker::{rectangle_tracker_subscription, RectangleTracker, RectangleUpdate}, - scrollable, search_input, secure_input, segmented_button, segmented_selection, settings, - text, text_input, - }, - Element, -}; -use cosmic_time::{anim, chain, id, once_cell::sync::Lazy, Instant, Timeline}; -use std::{ - sync::atomic::{AtomicU32, Ordering}, - vec, -}; - -static DEBUG_TOGGLER: Lazy = Lazy::new(id::Toggler::unique); -static TOGGLER: Lazy = Lazy::new(id::Toggler::unique); -static CARDS: Lazy = Lazy::new(id::Cards::unique); - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Page { - Demo, - WiFi, - Networking, - Bluetooth, - Desktop, - InputDevices, - Displays, - PowerAndBattery, - Sound, - PrintersAndScanners, - PrivacyAndSecurity, - SystemAndAccounts, - UpdatesAndRecovery, - TimeAndLanguage, - Accessibility, - Applications, -} - -impl Page { - //TODO: translate - pub fn title(&self) -> &'static str { - use Page::*; - match self { - Demo => "Demo", - WiFi => "Wi-Fi", - Networking => "Networking", - Bluetooth => "Bluetooth", - Desktop => "Desktop", - InputDevices => "Input Devices", - Displays => "Displays", - PowerAndBattery => "Power & Battery", - Sound => "Sound", - PrintersAndScanners => "Printers & Scanners", - PrivacyAndSecurity => "Privacy & Security", - SystemAndAccounts => "System & Accounts", - UpdatesAndRecovery => "Updates & Recovery", - TimeAndLanguage => "Time & Language", - Accessibility => "Accessibility", - Applications => "Applications", - } - } - - pub fn icon_name(&self) -> &'static str { - use Page::*; - match self { - Demo => "document-properties-symbolic", - WiFi => "network-wireless-symbolic", - Networking => "network-workgroup-symbolic", - Bluetooth => "bluetooth-active-symbolic", - Desktop => "video-display-symbolic", - InputDevices => "input-keyboard-symbolic", - Displays => "preferences-desktop-display-symbolic", - PowerAndBattery => "battery-full-charged-symbolic", - Sound => "multimedia-volume-control-symbolic", - PrintersAndScanners => "printer-symbolic", - PrivacyAndSecurity => "preferences-system-privacy-symbolic", - SystemAndAccounts => "system-users-symbolic", - UpdatesAndRecovery => "software-update-available-symbolic", - TimeAndLanguage => "preferences-system-time-symbolic", - Accessibility => "preferences-desktop-accessibility-symbolic", - Applications => "preferences-desktop-apps-symbolic", - } - } -} - -impl Default for Page { - fn default() -> Page { - //TODO: what should the default page be? - Page::Desktop - } -} - -static WINDOW_WIDTH: AtomicU32 = AtomicU32::new(0); -const BREAK_POINT: u32 = 900; - -#[derive(Default)] -pub struct Window { - title: String, - page: Page, - debug: bool, - theme: Theme, - slider_value: f32, - checkbox_value: bool, - toggler_value: bool, - cards_value: bool, - pick_list_selected: Option<&'static str>, - nav_bar_pages: segmented_button::SingleSelectModel, - nav_bar_toggled_condensed: bool, - nav_bar_toggled: bool, - show_minimize: bool, - show_maximize: bool, - exit: bool, - rectangle_tracker: Option>, - pub selection: segmented_button::SingleSelectModel, - timeline: Timeline, - input_value: String, - secure_input_visible: bool, -} - -impl Window { - /// Adds a page to the model we use for the navigation bar. - fn insert_page(&mut self, page: Page) -> segmented_button::SingleSelectEntityMut { - self.nav_bar_pages - .insert() - .text(page.title()) - .icon(icon::handle::from_name(page.icon_name()).icon()) - .data(page) - } - - fn is_condensed(&self) -> bool { - WINDOW_WIDTH.load(Ordering::Relaxed) < BREAK_POINT - } - - pub fn nav_bar_toggled(mut self, toggled: bool) -> Self { - self.nav_bar_toggled = toggled; - self - } - - fn page(&mut self, page: Page) { - self.nav_bar_toggled_condensed = false; - self.page = page; - } - - 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, Debug)] -pub enum Message { - Page(Page), - Debug(bool), - ThemeChanged(Theme), - ButtonPressed, - SliderChanged(f32), - CheckboxToggled(bool), - TogglerToggled(bool), - CardsToggled(bool), - PickListSelected(&'static str), - RowSelected(usize), - Close, - ToggleNavBar, - ToggleNavBarCondensed, - Drag, - Minimize, - Maximize, - Rectangle(RectangleUpdate), - NavBar(segmented_button::Entity), - Ignore, - Selection(segmented_button::Entity), - Tick(Instant), - InputChanged(String), - ToggleVisible, -} - -impl Window { - fn update_togglers(&mut self) { - let timeline = &mut self.timeline; - - let chain = if self.toggler_value { - chain::Toggler::on(TOGGLER.clone(), 1.) - } else { - chain::Toggler::off(TOGGLER.clone(), 1.) - }; - timeline.set_chain(chain); - - let chain = if self.debug { - chain::Toggler::on(DEBUG_TOGGLER.clone(), 1.) - } else { - chain::Toggler::off(DEBUG_TOGGLER.clone(), 1.) - }; - timeline.set_chain(chain); - - timeline.start(); - } - - fn update_cards(&mut self) { - let timeline = &mut self.timeline; - let chain = if self.cards_value { - chain::Cards::on(CARDS.clone(), 1.) - } else { - chain::Cards::off(CARDS.clone(), 1.) - }; - timeline.set_chain(chain); - timeline.start(); - } -} - -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() - .nav_bar_toggled(true) - .show_maximize(true) - .show_minimize(true); - window.selection = segmented_button::Model::builder() - .insert(|b| b.text("Choice A").activate()) - .insert(|b| b.text("Choice B")) - .insert(|b| b.text("Choice C")) - .build(); - window.slider_value = 50.0; - // window.theme = Theme::Light; - window.pick_list_selected = Some("Option 1"); - window.title = String::from("COSMIC Design System - Iced"); - - window.insert_page(Page::Demo); - window.insert_page(Page::WiFi); - window.insert_page(Page::Networking); - window.insert_page(Page::Bluetooth); - window.insert_page(Page::Desktop).activate(); - window.insert_page(Page::InputDevices); - window.insert_page(Page::Displays); - window.insert_page(Page::PowerAndBattery); - window.insert_page(Page::Sound); - window.insert_page(Page::PrintersAndScanners); - window.insert_page(Page::PrivacyAndSecurity); - window.insert_page(Page::SystemAndAccounts); - window.insert_page(Page::TimeAndLanguage); - window.insert_page(Page::Accessibility); - window.insert_page(Page::Applications); - - (window, Command::none()) - } - - fn title(&self) -> String { - self.title.clone() - } - - fn update(&mut self, message: Message) -> iced::Command { - match message { - Message::NavBar(key) => { - if let Some(page) = self.nav_bar_pages.data::(key).cloned() { - self.nav_bar_pages.activate(key); - self.page(page); - } - } - Message::Page(page) => self.page = page, - Message::Debug(debug) => { - self.debug = debug; - self.update_togglers(); - } - 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; - self.update_togglers(); - } - Message::CardsToggled(value) => { - self.cards_value = value; - self.update_cards(); - } - Message::PickListSelected(value) => self.pick_list_selected = Some(value), - Message::Close => self.exit = true, - Message::ToggleNavBar => self.nav_bar_toggled = !self.nav_bar_toggled, - Message::ToggleNavBarCondensed => { - self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed - } - Message::Drag => return start_drag_window(window::Id(0)), - Message::Minimize => return set_mode_window(window::Id(0), window::Mode::Hidden), - Message::Maximize => return toggle_maximize(window::Id(0)), - Message::RowSelected(row) => println!("Selected row {row}"), - Message::Rectangle(r) => match r { - RectangleUpdate::Rectangle(_) => {} - RectangleUpdate::Init(t) => { - self.rectangle_tracker.replace(t); - } - }, - Message::Ignore => {} - Message::Selection(key) => self.selection.activate(key), - Message::Tick(now) => self.timeline.now(now), - Message::InputChanged(v) => { - self.input_value = v; - } - Message::ToggleVisible => { - self.secure_input_visible = !self.secure_input_visible; - } - } - - Command::none() - } - - fn view(&self, _: window::Id) -> Element { - let (nav_bar_message, nav_bar_toggled) = if self.is_condensed() { - ( - Message::ToggleNavBarCondensed, - self.nav_bar_toggled_condensed, - ) - } else { - (Message::ToggleNavBar, self.nav_bar_toggled) - }; - - let mut header = header_bar() - .title("COSMIC Design System - Iced") - .on_close(Message::Close) - .on_drag(Message::Drag) - .start( - nav_bar_toggle() - .on_toggle(nav_bar_message) - .active(nav_bar_toggled), - ); - - if self.show_maximize { - header = header.on_maximize(Message::Maximize); - } - - if self.show_minimize { - header = header.on_minimize(Message::Minimize); - } - - let header = Into::>::into(header).debug(self.debug); - - let mut widgets = Vec::with_capacity(2); - - if nav_bar_toggled { - let mut nav_bar = nav_bar(&self.nav_bar_pages, Message::NavBar); - - if !self.is_condensed() { - nav_bar = nav_bar.max_width(300); - } - - let nav_bar: Element<_> = nav_bar.into(); - widgets.push(nav_bar.debug(self.debug)); - } - - if !nav_bar_toggled { - let content: Element<_> = settings::view_column(vec![ - settings::view_section("Debug") - .add(settings::item( - "Debug layout", - container(anim!( - //toggler - DEBUG_TOGGLER, - &self.timeline, - String::from("Debug layout"), - self.debug, - |_chain, enable| { Message::Debug(enable) }, - )), - )) - .into(), - settings::view_section("Controls") - .add(settings::item( - "Toggler", - anim!( - //toggler - TOGGLER, - &self.timeline, - None, - self.toggler_value, - |_chain, enable| { Message::TogglerToggled(enable) }, - ), - )) - .add(settings::item( - "Pick List (TODO)", - pick_list( - vec!["Option 1", "Option 2", "Option 3", "Option 4"], - self.pick_list_selected, - Message::PickListSelected, - ) - .text_size(14.0), - )) - .add(settings::item( - "Slider", - slider(0.0..=100.0, self.slider_value, Message::SliderChanged) - .width(Length::Fixed(250.0)), - )) - .add(settings::item( - "Progress", - progress_bar(0.0..=100.0, self.slider_value) - .width(Length::Fixed(250.0)) - .height(Length::Fixed(4.0)), - )) - .add(settings::item( - "Segmented Button", - segmented_selection::horizontal(&self.selection) - .on_activate(Message::Selection), - )) - .add(settings::item( - "Cards", - cosmic_container::container(anim!( - //cards - CARDS, - &self.timeline, - vec![ - text("Card 1").size(24).width(Length::Fill).into(), - text("Card 2").size(24).width(Length::Fill).into(), - text("Card 3").size(24).width(Length::Fill).into(), - text("Card 4").size(24).width(Length::Fill).into(), - ], - Message::Ignore, - |_, e| Message::CardsToggled(e), - "Show More", - "Show Less", - "Clear All", - None, - self.cards_value, - )) - .layer(cosmic::cosmic_theme::Layer::Secondary) - .padding(16) - .style(cosmic::theme::Container::Secondary), - )) - .add(settings::item( - "Text Input", - text_input("test", &self.input_value) - .width(Length::Fill) - .on_input(Message::InputChanged), - )) - .add(settings::item( - "Text Input", - secure_input( - "test", - &self.input_value, - Some(Message::ToggleVisible), - !self.secure_input_visible, - ) - .label("Test Secure Input Label") - .helper_text("password") - .width(Length::Fill) - .on_input(Message::InputChanged), - )) - .add(settings::item( - "Text Input", - search_input( - "search for stuff", - &self.input_value, - Some(Message::InputChanged("".to_string())), - ) - .width(Length::Fill) - .on_input(Message::InputChanged), - )) - .add(settings::item( - "Text Input", - inline_input(&self.input_value) - .width(Length::Fill) - .on_input(Message::InputChanged), - )) - .into(), - ]) - .into(); - - widgets.push( - scrollable(row![ - horizontal_space(Length::Fill), - content.debug(self.debug), - horizontal_space(Length::Fill), - ]) - .into(), - ); - } - - let content = container(row(widgets)) - .padding([0, 8, 8, 8]) - .width(Length::Fill) - .height(Length::Fill) - .style(theme::Container::Background) - .into(); - - column(vec![header, content]).into() - } - - fn should_exit(&self) -> bool { - self.exit - } - - fn theme(&self) -> Theme { - self.theme.clone() - } - - fn close_requested(&self, _id: window::Id) -> Self::Message { - Message::Close - } - fn subscription(&self) -> iced::Subscription { - Subscription::batch(vec![ - rectangle_tracker_subscription(0).map(|(_, e)| Self::Message::Rectangle(e)), - self.timeline - .as_subscription() - .map(|(_, instant)| Self::Message::Tick(instant)), - ]) - } - - fn style(&self) -> ::Style { - cosmic::theme::Application::Custom(Box::new(|theme| application::Appearance { - background_color: Color::TRANSPARENT, - icon_color: theme.cosmic().on_bg_color().into(), - text_color: theme.cosmic().on_bg_color().into(), - })) - } -} diff --git a/examples/design-demo/Cargo.toml b/examples/design-demo/Cargo.toml new file mode 100644 index 00000000..5d4f9e22 --- /dev/null +++ b/examples/design-demo/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cosmic-design-demo" +version = "0.1.0" +edition = "2021" + +[dependencies] +fraction = "0.13.1" +tracing = "0.1.37" +tracing-subscriber = "0.3.17" + +[dependencies.libcosmic] +path = "../../" +default-features = false +features = ["debug", "winit", "tokio"] + +[dependencies.cosmic-time] +# git = "https://github.com/pop-os/cosmic-time" +# branch = "icon-color" +path = "../../../cosmic-time" +default-features = false +features = ["libcosmic", "once_cell"] diff --git a/examples/design-demo/resources/com.system76.CosmicDesignDemo.desktop b/examples/design-demo/resources/com.system76.CosmicDesignDemo.desktop new file mode 100644 index 00000000..fb19890c --- /dev/null +++ b/examples/design-demo/resources/com.system76.CosmicDesignDemo.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Name=COSMIC Design System Demo +Type=Application +Exec=cosmic-design-demo +Terminal=true +Categories=COSMIC; +Keywords=COSMIC;Design;Demo; +Icon=preferences-pop-desktop-appearance +StartupNotify=true +NoDisplay=false \ No newline at end of file diff --git a/examples/design-demo/src/buttons.rs b/examples/design-demo/src/buttons.rs new file mode 100644 index 00000000..3fe2e6f0 --- /dev/null +++ b/examples/design-demo/src/buttons.rs @@ -0,0 +1,352 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::{App, Message}; +use cosmic::iced_core::Alignment; +use cosmic::widget::{button, column, icon, row, text}; +use cosmic::Element; + +impl App +where + Self: cosmic::Application, +{ + pub fn view_buttons(&self) -> Element { + column() + .spacing(24) + .push(text::title1("Text Buttons")) + // Suggested button header + .push( + column() + .spacing(8) + .push(text::title3("Suggested Button")) + .push(text("Highest level of attention, there should only be one primary button used on the page.").size(14.0)) + ) + // Suggested button demo + .push( + row() + .spacing(36) + .push(button::suggested("Label").on_press(Message::Clicked)) + .push(button::suggested("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) + .push(button::suggested("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) + .push(button::suggested("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) + .push( + button::suggested("Label") + .on_press(Message::Clicked) + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + .push( + button::suggested("Disabled") + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + ) + // Destructive button header + .push( + column() + .spacing(8) + .push(text::title3("Destructive Button")) + .push(text("Highest level of attention, there should only be one primary button used on the page.").size(14.0)) + ) + // Destructive button demo + .push( + row() + .spacing(36) + .push(button::destructive("Label").on_press(Message::Clicked)) + .push(button::destructive("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) + .push(button::destructive("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) + .push(button::destructive("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) + .push( + button::destructive("Label") + .on_press(Message::Clicked) + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + .push( + button::destructive("Disabled") + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + ) + // Standard button header + .push( + column() + .spacing(8) + .push(text::title3("Standard Button")) + .push( + text( + "Requires less attention from the user. Could be more \ + than one button on the page, if necessary." + ) + .size(14.0) + ) + ) + // Standard button demo + .push( + row() + .spacing(36) + .push(button::standard("Label").on_press(Message::Clicked)) + .push(button::standard("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) + .push(button::standard("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) + .push(button::standard("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) + .push( + button::standard("Label") + .on_press(Message::Clicked) + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + .push( + button::standard("Disabled") + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + ) + // Text button header + .push( + column() + .spacing(8) + .push(text::title3("Text Button")) + .push(text( + "Lowest priority actions, especially when presenting multiple options. Because text buttons \ + don’t have a visible container in their default state, they don’t distract from nearby \ + content. But they are also more difficult to recognize because of that." + ).size(14.0)) + ) + // Text button demo + .push( + row() + .spacing(36) + .push(button::text("Label").on_press(Message::Clicked)) + .push(button::text("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) + .push(button::text("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) + .push(button::text("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) + .push( + button::text("Label") + .on_press(Message::Clicked) + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + .push( + button::text("Disabled") + .leading_icon(self.app_icon.clone()) + .trailing_icon(self.trailing_icon.clone()) + ) + ) + // Icon buttons + .push(text::title1("Icon Buttons")) + .push(view_icon_buttons(self.bt_icon.clone())) + .push(text::title1("App Icon Buttons")) + .push(view_icon_buttons(self.app_icon.clone())) + .push(text::title1("Hyperlinks")) + .push(text::body("All the buttons have Default, Hover, Pressed, and Disabled states. Buttons in any of the states can have a Focused indicator signifying the button is ready to interact.")) + .push( + row() + .spacing(36) + .push(button::link("Hyperlink").on_press(Message::Clicked)) + .push(button::link("Hyperlink").trailing_icon(true).on_press(Message::Clicked)) + .push(button::link("Hyperlink")) + .push(button::link("Hyperlink").trailing_icon(true)) + ) + .into() + } +} + +fn view_icon_buttons(icon: icon::Handle) -> impl Into> { + row() + .spacing(36) + // Without Labels + .push( + column() + .spacing(24) + .align_items(Alignment::Center) + .push( + button::icon(icon.clone()) + .extra_small() + .on_press(Message::Clicked) + .tooltip("Extra small icon button"), + ) + .push( + button::icon(icon.clone()) + .on_press(Message::Clicked) + .tooltip("Small icon button"), + ) + .push( + button::icon(icon.clone()) + .medium() + .on_press(Message::Clicked) + .tooltip("Medium icon button"), + ) + .push( + button::icon(icon.clone()) + .large() + .on_press(Message::Clicked) + .tooltip("Large icon button"), + ) + .push( + button::icon(icon.clone()) + .extra_large() + .on_press(Message::Clicked) + .tooltip("Extra large icon button"), + ), + ) + // With Labels + .push( + column() + .spacing(24) + .align_items(Alignment::Center) + .push( + button::icon(icon.clone()) + .extra_small() + .on_press(Message::Clicked) + .tooltip("Extra small icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .on_press(Message::Clicked) + .tooltip("Small icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .medium() + .on_press(Message::Clicked) + .tooltip("Medium icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .large() + .on_press(Message::Clicked) + .tooltip("Large icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .extra_large() + .on_press(Message::Clicked) + .tooltip("Extra large icon button") + .label("Label"), + ), + ) + // Disabled + .push( + column() + .spacing(24) + .align_items(Alignment::Center) + .push( + button::icon(icon.clone()) + .extra_small() + .tooltip("Extra small icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .tooltip("Small icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .medium() + .tooltip("Medium icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .large() + .tooltip("Large icon button") + .label("Label"), + ) + .push( + button::icon(icon.clone()) + .extra_large() + .tooltip("Extra large icon button") + .label("Label"), + ), + ) + // Vertical layout + .push( + column() + .spacing(24) + .align_items(Alignment::Center) + .push( + button::icon(icon.clone()) + .extra_small() + .on_press(Message::Clicked) + .tooltip("Extra small icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .on_press(Message::Clicked) + .tooltip("Small icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .medium() + .on_press(Message::Clicked) + .tooltip("Medium icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .large() + .on_press(Message::Clicked) + .tooltip("Large icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .extra_large() + .on_press(Message::Clicked) + .tooltip("Extra large icon button") + .label("Label") + .vertical(true), + ), + ) + // Vertical disabled + .push( + column() + .spacing(24) + .align_items(Alignment::Center) + .push( + button::icon(icon.clone()) + .extra_small() + .tooltip("Extra small icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .tooltip("Small icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .medium() + .tooltip("Medium icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon.clone()) + .large() + .tooltip("Large icon button") + .label("Label") + .vertical(true), + ) + .push( + button::icon(icon) + .extra_large() + .tooltip("Extra large icon button") + .label("Label") + .vertical(true), + ), + ) +} diff --git a/examples/design-demo/src/cards.rs b/examples/design-demo/src/cards.rs new file mode 100644 index 00000000..e10e31b6 --- /dev/null +++ b/examples/design-demo/src/cards.rs @@ -0,0 +1,52 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::{App, Message}; +use cosmic::iced_core::Length; +use cosmic::widget::{container, cosmic_container, text}; +use cosmic::Element; +use cosmic_time::{anim, chain, id, once_cell::sync::Lazy}; + +static CARDS: Lazy = Lazy::new(id::Cards::unique); + +impl App +where + Self: cosmic::Application, +{ + pub fn update_cards(&mut self) { + let timeline = &mut self.timeline; + let chain = if self.cards_value { + chain::Cards::on(CARDS.clone(), 1.) + } else { + chain::Cards::off(CARDS.clone(), 1.) + }; + timeline.set_chain(chain); + timeline.start(); + } + + pub fn view_cards(&self) -> Element { + container( + cosmic_container::container(anim!( + CARDS, + &self.timeline, + vec![ + text("Card 1").size(24).width(Length::Fill).into(), + text("Card 2").size(24).width(Length::Fill).into(), + text("Card 3").size(24).width(Length::Fill).into(), + text("Card 4").size(24).width(Length::Fill).into(), + ], + Message::Ignore, + |_, e| Message::CardsToggled(e), + "Show More", + "Show Less", + "Clear All", + None, + self.cards_value, + )) + .layer(cosmic::cosmic_theme::Layer::Secondary) + .padding(16) + .style(cosmic::theme::Container::Secondary), + ) + .into() + } +} diff --git a/examples/design-demo/src/debug.rs b/examples/design-demo/src/debug.rs new file mode 100644 index 00000000..a1f6eaea --- /dev/null +++ b/examples/design-demo/src/debug.rs @@ -0,0 +1,138 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::{App, Message}; +use cosmic::iced::Length; +use cosmic::prelude::*; +use cosmic::theme::ThemeType; +use cosmic::widget::{ + column, container, divider, list, pick_list, radio, row, settings, spin_button, text, +}; +use cosmic::Element; +use cosmic_time::{anim, id, once_cell::sync::Lazy}; + +pub static DEBUG_TOGGLER: Lazy = Lazy::new(id::Toggler::unique); + +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)] +pub enum ThemeVariant { + Custom, + Dark, + HighContrastDark, + HighContrastLight, + Light, + System, +} + +impl ThemeVariant { + const fn as_str(self) -> &'static str { + match self { + Self::Custom => "Custom", + Self::Dark => "Dark", + Self::HighContrastDark => "HighContrastDark", + Self::HighContrastLight => "HighContrastLight", + Self::Light => "Light", + Self::System => "System", + } + } +} + +impl From<&ThemeType> for ThemeVariant { + fn from(theme: &ThemeType) -> Self { + match theme { + ThemeType::Light => ThemeVariant::Light, + ThemeType::Dark => ThemeVariant::Dark, + ThemeType::HighContrastDark => ThemeVariant::HighContrastDark, + ThemeType::HighContrastLight => ThemeVariant::HighContrastLight, + ThemeType::Custom(_) => ThemeVariant::Custom, + ThemeType::System(_) => ThemeVariant::System, + } + } +} + +impl From for ThemeVariant { + fn from(theme: ThemeType) -> Self { + ThemeVariant::from(&theme) + } +} + +const THEME_CHOICES: &[ThemeVariant] = &[ + ThemeVariant::Light, + ThemeVariant::Dark, + ThemeVariant::System, + ThemeVariant::HighContrastLight, + ThemeVariant::HighContrastDark, + ThemeVariant::Custom, +]; + +impl App +where + Self: cosmic::Application, +{ + pub fn view_debug(&self) -> Element { + let mut theme_choices = THEME_CHOICES.iter().cloned().map(|theme| { + radio( + theme.as_str(), + theme, + if ThemeVariant::from(cosmic::theme::active_type()) == theme { + Some(theme) + } else { + None + }, + Message::ThemeChanged, + ) + .width(200) + }); + + column() + .spacing(24) + .push( + column() + .spacing(8) + .push(text::heading("Change Theme")) + .push(list::container( + column() + .spacing(12) + .padding([0, 18]) + .push(row().extend(theme_choices.by_ref().take(3))) + .push(row().extend(theme_choices)) + .apply(container) + .center_x() + .width(Length::Fill), + )), + ) + .push( + column() + .spacing(8) + .push(text::heading("Debug Options")) + .push(list::container( + column() + .spacing(12) + .push( + container(anim!( + DEBUG_TOGGLER, + &self.timeline, + String::from("Debug layout"), + self.core.debug, + |_chain, enable| { Message::DebugToggled(enable) }, + )) + .padding([0, 18]), + ) + .push(divider::horizontal::light()) + .push(settings::item( + "Scaling Factor", + spin_button(&self.scale_factor_str, Message::ScalingFactorChanged), + )) + .push(divider::horizontal::light()) + .push(settings::item( + "Layer", + pick_list( + &["Default", "Primary", "Secondary"][..], + Some(self.layer_selection), + Message::LayerSelect, + ), + )), + )), + ) + .into() + } +} diff --git a/examples/design-demo/src/inputs.rs b/examples/design-demo/src/inputs.rs new file mode 100644 index 00000000..27d684ce --- /dev/null +++ b/examples/design-demo/src/inputs.rs @@ -0,0 +1,193 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::{App, Message}; +use cosmic::iced_core::{Alignment, Length}; +use cosmic::widget::{ + checkbox, column, container, inline_input, pick_list, progress_bar, row, search_input, + secure_input, segmented_selection, slider, text, text_input, view_switcher, +}; +use cosmic::{Apply, Element}; + +static PLACEHOLDER_TEXT: &str = "placeholder text"; + +impl App +where + Self: cosmic::Application, +{ + pub fn view_text_input(&self) -> Element { + column() + .spacing(24) + .push(text::title1("Checkbox")) + .push( + checkbox( + "Checkbox", + self.checkbox_value, + Message::CheckboxToggled, + ) + ) + .push(text::title1("Pick List")) + .push( + pick_list( + &self.pick_list_options, + self.pick_list_selected, + Message::PickListSelected, + ) + ) + .push(text::title1("Progress Bar")) + .push( + progress_bar(0.0..=100.0, self.slider_value) + .width(Length::Fixed(250.0)) + .height(Length::Fixed(4.0)), + ) + .push(text::title1("Segmented Buttons")) + .push(text::title2("Segmented Selection")) + .push( + row() + .spacing(12) + .push(text::body("Horizontal")) + .push( + segmented_selection::horizontal(&self.selection) + .on_activate(Message::Selection), + ) + ) + .push( + row() + .spacing(12) + .align_items(Alignment::Center) + .push(text::body("Vertical")) + .push( + segmented_selection::vertical(&self.selection) + .on_activate(Message::Selection), + ) + ) + + .push(text::title2("View Switcher")) + .push( + row() + .spacing(12) + .push(text::body("Horizontal")) + .push( + view_switcher::horizontal(&self.selection) + .on_activate(Message::Selection), + ) + ) + .push( + row() + .spacing(12) + .align_items(Alignment::Center) + .push(text::body("Vertical")) + .push( + view_switcher::vertical(&self.selection) + .on_activate(Message::Selection), + ) + ) + .push(text::title1("Slider")) + .push( + slider(0.0..=100.0, self.slider_value, Message::SliderChanged) + .width(Length::Fixed(250.0)) + .height(38) + ) + .push(text::title1("Spin Button")) + .push(text::title1("Text Inputs")) + .push(text::body("Collection of different text input variants.")) + .push(text::title2("Text Input")) + .push(text::body("The standard text input widget.")) + .push( + row() + .align_items(Alignment::Center) + .push(text::body("Enabled")) + .spacing(12) + .push( + text_input(PLACEHOLDER_TEXT, &self.text_input_value) + .width(Length::Fill) + .on_input(Message::TextInputChanged) + ) + .push(text::body("Disabled")) + .push( + text_input(PLACEHOLDER_TEXT, &self.text_input_value) + .width(Length::Fill) + ) + ) + .push(text::title2("Search Input")) + .push(text::body("Search inputs should be used where search functionality is desired. They differ from the standard text input by displaying a search icon and a clickable search clear button")) + .push( + row() + .align_items(Alignment::Center) + .push(text::body("Enabled")) + .spacing(12) + .push( + search_input( + PLACEHOLDER_TEXT, + &self.text_input_value, + Some(Message::TextInputChanged("".to_string())), + ) + .width(Length::Fill) + .on_input(Message::TextInputChanged) + ) + .push(text::body("Disabled")) + .push( + search_input( + "", + &self.text_input_value, + Some(Message::TextInputChanged("".to_string())), + ) + .width(Length::Fill) + ) + ) + .push(text::title2("Secure Input")) + .push( + row() + .align_items(Alignment::Center) + .push(text::body("Enabled")) + .spacing(12) + .push( + secure_input( + PLACEHOLDER_TEXT, + &self.text_input_value, + Some(Message::SecureInputToggled), + !self.secure_input_visible, + ) + .label("Test Secure Input Label") + .helper_text("Helper Text") + .width(Length::Fill) + .on_input(Message::TextInputChanged) + ) + .push(text::body("Disabled")) + .push( + secure_input( + "", + &self.text_input_value, + Some(Message::SecureInputToggled), + !self.secure_input_visible, + ) + .label("Test Secure Input Label") + .helper_text("Helper Text") + .width(Length::Fill) + ) + + ) + .push(text::title2("Inline Input")) + .push(text::body("Inline Text Input should be used only inside other widgets, like ListItem or in situations when the input is a quick and transient action (for example, to quickly enter a value in some sort of editing program). ")) + .push( + row() + .align_items(Alignment::Center) + .push(text::body("Enabled")) + .spacing(12) + .push( + inline_input(&self.text_input_value) + .width(Length::Fill) + .on_input(Message::TextInputChanged) + ) + .push(text::body("Disabled")) + .push( + inline_input(&self.text_input_value) + .width(Length::Fill) + ) + ) + .push(text::title1("Toggle")) + .apply(container) + .max_width(800) + .into() + } +} diff --git a/examples/design-demo/src/main.rs b/examples/design-demo/src/main.rs new file mode 100644 index 00000000..76022755 --- /dev/null +++ b/examples/design-demo/src/main.rs @@ -0,0 +1,356 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +mod buttons; +mod cards; +mod debug; +mod inputs; +mod typography; + +use cosmic::app::{Command, Core, Settings}; +use cosmic::cosmic_theme::palette::{rgb::Rgb, Srgba}; +use cosmic::cosmic_theme::ThemeBuilder; +use cosmic::iced::Length; +use cosmic::widget::{ + button, column, container, icon, nav_bar, scrollable, segmented_button, spin_button, +}; +use cosmic::{executor, ApplicationExt, Apply, Element, Theme}; +use cosmic_time::Timeline; +use debug::ThemeVariant; +use fraction::{Decimal, ToPrimitive}; +use std::sync::Arc; + +#[derive(Clone, Copy)] +pub enum Page { + Buttons, + Cards, + Inputs, + Typography, +} + +impl Page { + const fn as_str(self) -> &'static str { + match self { + Page::Buttons => "Buttons", + Page::Cards => "Cards", + Page::Inputs => "Inputs", + Page::Typography => "Typography", + } + } +} + +/// Runs application with these settings +#[rustfmt::skip] +fn main() -> Result<(), Box> { + let settings = Settings::default() + .antialiasing(true) + .client_decorations(true) + .debug(false) + .size((1280, 768)) + .theme(cosmic::Theme::dark()); + + cosmic::app::run::(settings, ())?; + + Ok(()) +} + +/// Messages that are used specifically by our [`App`]. +#[derive(Clone, Debug)] +pub enum Message { + CardsToggled(bool), + CheckboxToggled(bool), + Clicked, + DebugToggled(bool), + Ignore, + LayerSelect(&'static str), + PickListSelected(&'static str), + ScalingFactorChanged(spin_button::Message), + SecureInputToggled, + Selection(segmented_button::Entity), + SliderChanged(f32), + TextInputChanged(String), + ThemeChanged(ThemeVariant), + Tick(cosmic_time::Instant), + TogglerToggled(bool), +} + +/// The [`App`] stores application-specific state. +pub struct App { + core: Core, + nav_model: nav_bar::Model, + layer_selection: &'static str, + + // cosmic-time dependency + timeline: Timeline, + + // Buttons page + app_icon: icon::Handle, + bt_icon: icon::Handle, + leading_icon: icon::Handle, + trailing_icon: icon::Handle, + + // Cards page + cards_value: bool, + + // Debug page + scale_factor: spin_button::Model, + scale_factor_str: String, + + // Inputs page + checkbox_value: bool, + pick_list_selected: Option<&'static str>, + pick_list_options: Vec<&'static str>, + text_input_value: String, + secure_input_visible: bool, + selection: segmented_button::SingleSelectModel, + slider_value: f32, +} + +/// Implement [`cosmic::Application`] to integrate with COSMIC. +impl cosmic::Application for App { + /// Default async executor to use with the app. + type Executor = executor::Default; + + /// Argument received [`cosmic::Application::new`]. + type Flags = (); + + /// Message type specific to our [`App`]. + type Message = Message; + + /// The unique application ID to supply to the window manager. + const APP_ID: &'static str = "com.system76.CosmicDesignDemo"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + /// Creates the application, and optionally emits command on initialize. + fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + let nav_model = nav_bar::Model::builder() + .insert(|e| e.text(Page::Typography.as_str()).data(Page::Typography)) + .insert(|e| { + e.text(Page::Buttons.as_str()) + .data(Page::Buttons) + .activate() + }) + .insert(|e| e.text(Page::Cards.as_str()).data(Page::Cards)) + .insert(|e| e.text(Page::Inputs.as_str()).data(Page::Inputs)) + .build(); + + let mut app = App { + nav_model, + layer_selection: "Default", + + // cosmic-time dependency + timeline: Timeline::default(), + + // Buttons page + app_icon: icon::from_name("firefox").into(), + bt_icon: icon::from_name("bluetooth-active-symbolic").size(16).into(), + leading_icon: icon::from_name("document-save-symbolic").size(16).into(), + trailing_icon: button::link::icon(), + + // Cards page + cards_value: false, + + // Debug page + scale_factor: spin_button::Model::default() + .value(core.scale_factor()) + .min(0.5) + .max(4.0) + .step(0.25), + scale_factor_str: core.scale_factor().to_string(), + + // Inputs page + checkbox_value: false, + pick_list_selected: Some("Option 1"), + pick_list_options: vec!["Option 1", "Option 2", "Option 3", "Option 4"], + text_input_value: String::new(), + secure_input_visible: false, + selection: segmented_button::Model::builder() + .insert(|b| b.text("Choice A").activate()) + .insert(|b| b.text("Choice B")) + .insert(|b| b.text("Choice C")) + .build(), + slider_value: 0.0, + + core, + }; + + let command = app.update_title(); + + (app, command) + } + + /// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. + fn nav_model(&self) -> Option<&nav_bar::Model> { + Some(&self.nav_model) + } + + /// Called when a navigation item is selected. + fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { + self.nav_model.activate(id); + self.update_title() + } + + /// Handle application events here. + fn update(&mut self, message: Self::Message) -> Command { + match message { + Message::Tick(now) => self.timeline.now(now), + + Message::TextInputChanged(input) => { + self.text_input_value = input; + } + + Message::Clicked => { + eprintln!("button clicked"); + } + + Message::CardsToggled(value) => { + self.cards_value = value; + self.update_cards(); + } + + Message::DebugToggled(value) => { + self.core.debug = value; + self.update_togglers(); + } + + Message::LayerSelect(selection) => { + self.layer_selection = selection; + } + + Message::SecureInputToggled => { + self.secure_input_visible = !self.secure_input_visible; + } + + Message::Selection(key) => self.selection.activate(key), + + Message::CheckboxToggled(value) => { + self.checkbox_value = value; + } + + Message::SliderChanged(value) => { + self.slider_value = value; + } + + Message::TogglerToggled(value) => { + eprintln!("card toggler: {value}"); + } + + Message::PickListSelected(value) => self.pick_list_selected = Some(value), + + Message::ThemeChanged(theme) => { + return cosmic::app::command::set_theme(match theme { + ThemeVariant::Light => Theme::light(), + ThemeVariant::Dark => Theme::dark(), + ThemeVariant::HighContrastDark => Theme::dark_hc(), + ThemeVariant::HighContrastLight => Theme::light_hc(), + ThemeVariant::Custom => Theme::custom(Arc::new( + ThemeBuilder::light() + .bg_color(Srgba::new(1.0, 0.9, 0.9, 1.0)) + .text_tint(Rgb::new(0.0, 1.0, 0.0)) + .neutral_tint(Rgb::new(0.0, 0.5, 1.0)) + .accent(Rgb::new(0.5, 0.1, 0.5)) + .success(Rgb::new(0.0, 0.5, 0.3)) + .warning(Rgb::new(0.894, 0.816, 0.039)) + .destructive(Rgb::new(0.890, 0.145, 0.420)) + .build(), + )), + ThemeVariant::System => cosmic::theme::system_preference(), + }); + } + + Message::ScalingFactorChanged(message) => { + self.scale_factor.update(message); + if let Some(factor) = self.scale_factor.value.to_f32() { + self.scale_factor_str = factor.to_string(); + return cosmic::app::command::set_scaling_factor(factor); + } + } + + Message::Ignore => (), + } + + Command::none() + } + + /// Creates a view after each update. + fn view(&self) -> Element { + // Generate a view for the active page. + let page_view = match self.nav_model.active_data::() { + Some(Page::Buttons) => self.view_buttons(), + Some(Page::Cards) => self.view_cards(), + Some(Page::Inputs) => self.view_text_input(), + Some(Page::Typography) => self.view_typography(), + None => cosmic::widget::text("Unknown page selected").into(), + }; + + column() + .spacing(24) + // Place debug view atop each page + .push(self.view_debug()) + // Insert page view beneath it + .push( + container(page_view) + .width(Length::Fill) + .style(match self.layer_selection { + "Primary" => cosmic::theme::Container::Primary, + "Secondary" => cosmic::theme::Container::Secondary, + _ => cosmic::theme::Container::default(), + }), + ) + // Wrap page views in container that expands up to 1000 px wide. + .apply(container) + .width(Length::Fill) + .max_width(1000) + // Wrap again to center-align the expanded container. + .apply(container) + .center_x() + .width(Length::Fill) + // Make it scrollable if height exceeds window. + .apply(scrollable) + .into() + } + + fn subscription(&self) -> cosmic::iced::Subscription { + cosmic::iced::Subscription::batch(vec![self + .timeline + .as_subscription() + .map(|(_, instant)| Message::Tick(instant))]) + } +} + +impl App +where + Self: cosmic::Application, +{ + fn active_page_title(&mut self) -> &str { + self.nav_model + .text(self.nav_model.active()) + .unwrap_or("Unknown Page") + } + + fn update_title(&mut self) -> Command { + let title = self.active_page_title().to_owned(); + let window_title = format!("{title} - COSMIC Design System"); + self.core.window.header_title = title.clone(); + self.set_title(window_title) + } + + fn update_togglers(&mut self) { + let chain = if self.core.debug { + cosmic_time::chain::Toggler::on(debug::DEBUG_TOGGLER.clone(), 1.) + } else { + cosmic_time::chain::Toggler::off(debug::DEBUG_TOGGLER.clone(), 1.) + }; + + self.timeline.set_chain(chain); + + self.timeline.start(); + } +} diff --git a/examples/design-demo/src/typography.rs b/examples/design-demo/src/typography.rs new file mode 100644 index 00000000..7135134f --- /dev/null +++ b/examples/design-demo/src/typography.rs @@ -0,0 +1,106 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::{App, Message}; +use cosmic::widget::{column, divider, row, text}; +use cosmic::Element; + +impl App +where + Self: cosmic::Application, +{ + pub fn view_typography(&self) -> Element { + // TODO: Implement with grid widget once grid widget is finished. + const WIDTH: u16 = 128; + static SAMPLE_TEXT: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + + column() + .spacing(32) + .push( + column() + .spacing(4) + .push( + row() + .push(text::heading("Text Style").width(WIDTH)) + .push(text::heading("Font Size").width(WIDTH)) + .push(text::heading("Line Height").width(WIDTH)) + .push(text::heading("Weight").width(WIDTH)) + .push(text::heading("Two-line Example").width(463)), + ) + .push(divider::horizontal::default()), + ) + .push( + row() + .push(text::title1("Title 1").width(WIDTH)) + .push(text::title1("32px").width(WIDTH)) + .push(text::title1("44px").width(WIDTH)) + .push(text::title1("Light (300)").width(WIDTH)) + .push(text::title1(SAMPLE_TEXT).width(463)), + ) + .push( + row() + .push(text::title2("Title 2").width(WIDTH)) + .push(text::title2("28px").width(WIDTH)) + .push(text::title2("36px").width(WIDTH)) + .push(text::title2("Regular (400)").width(WIDTH)) + .push(text::title2(SAMPLE_TEXT).width(376)), + ) + .push( + row() + .push(text::title3("Title 3").width(WIDTH)) + .push(text::title3("24px").width(WIDTH)) + .push(text::title3("32px").width(WIDTH)) + .push(text::title3("Regular (400)").width(WIDTH)) + .push(text::title3(SAMPLE_TEXT).width(376)), + ) + .push( + row() + .push(text::title4("Title 4").width(WIDTH)) + .push(text::title4("20px").width(WIDTH)) + .push(text::title4("28px").width(WIDTH)) + .push(text::title4("Regular (400)").width(WIDTH)) + .push(text::title4(SAMPLE_TEXT).width(335)), + ) + .push( + row() + .push(text::heading("Heading").width(WIDTH)) + .push(text::heading("14px").width(WIDTH)) + .push(text::heading("20px").width(WIDTH)) + .push(text::heading("Semibold (600)").width(WIDTH)) + .push(text::heading(SAMPLE_TEXT).width(234)), + ) + .push( + row() + .push(text::caption_heading("Caption Heading").width(WIDTH)) + .push(text::caption_heading("10px").width(WIDTH)) + .push(text::caption_heading("14px").width(WIDTH)) + .push(text::caption_heading("Semibold (600)").width(WIDTH)) + .push(text::caption_heading(SAMPLE_TEXT).width(164)), + ) + .push( + row() + .push(text::body("Body").width(WIDTH)) + .push(text::body("14px").width(WIDTH)) + .push(text::body("20px").width(WIDTH)) + .push(text::body("Regular (400)").width(WIDTH)) + .push(text::body(SAMPLE_TEXT).width(234)), + ) + .push( + row() + .push(text::caption("Caption").width(WIDTH)) + .push(text::caption("10px").width(WIDTH)) + .push(text::caption("14px").width(WIDTH)) + .push(text::caption("Regular (400)").width(WIDTH)) + .push(text::caption(SAMPLE_TEXT).width(164)), + ) + .push( + row() + .push(text::monotext("Monotext").width(WIDTH)) + .push(text::monotext("14px").width(WIDTH)) + .push(text::monotext("20px").width(WIDTH)) + .push(text::monotext("Regular (400)").width(WIDTH)) + .push(text::monotext(SAMPLE_TEXT).width(280)), + ) + .into() + } +} diff --git a/examples/design/Cargo.toml b/examples/design/Cargo.toml deleted file mode 100644 index eb89855d..00000000 --- a/examples/design/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "design" -version = "0.1.0" -edition = "2021" - -[dependencies] -tracing = "0.1.37" -tracing-subscriber = "0.3.17" - -[dependencies.libcosmic] -path = "../../" -default-features = false -features = ["debug", "winit", "tokio"] diff --git a/examples/design/src/main.rs b/examples/design/src/main.rs deleted file mode 100644 index 81c36efd..00000000 --- a/examples/design/src/main.rs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! Controls: buttons, radio buttons, toggles, etc. - -use cosmic::app::{Command, Core, Settings}; -use cosmic::widget::{button, column, container, icon, nav_bar, row, scrollable, text}; -use cosmic::{executor, iced, ApplicationExt, Apply, Element}; - -#[derive(Clone, Copy)] -pub enum Page { - Buttons, -} - -impl Page { - const fn as_str(self) -> &'static str { - match self { - Page::Buttons => "Buttons", - } - } -} - -/// Runs application with these settings -#[rustfmt::skip] -fn main() -> Result<(), Box> { - let settings = Settings::default() - .antialiasing(true) - .client_decorations(true) - .debug(false) - .size((1024, 768)) - .theme(cosmic::Theme::dark()); - - cosmic::app::run::(settings, &[Page::Buttons])?; - - Ok(()) -} - -/// Messages that are used specifically by our [`App`]. -#[derive(Clone, Debug)] -pub enum Message { - Clicked, -} - -/// The [`App`] stores application-specific state. -pub struct App { - core: Core, - nav_model: nav_bar::Model, - app_icon: icon::Handle, - bt_icon: icon::Handle, - leading_icon: icon::Handle, - trailing_icon: icon::Handle, -} - -/// Implement [`cosmic::Application`] to integrate with COSMIC. -impl cosmic::Application for App { - /// Default async executor to use with the app. - type Executor = executor::Default; - - /// Argument received [`cosmic::Application::new`]. - type Flags = &'static [Page]; - - /// Message type specific to our [`App`]. - type Message = Message; - - const APP_ID: &'static str = "org.cosmic.DesignDemo"; - - fn core(&self) -> &Core { - &self.core - } - - fn core_mut(&mut self) -> &mut Core { - &mut self.core - } - - /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, input: Self::Flags) -> (Self, Command) { - let mut nav_model = nav_bar::Model::default(); - - for &page in input { - nav_model.insert().text(page.as_str()).data(page); - } - - nav_model.activate_position(0); - - let mut app = App { - core, - app_icon: icon::handle::from_name("firefox").size(16).handle(), - bt_icon: icon::handle::from_name("bluetooth-active-symbolic") - .size(16) - .handle(), - leading_icon: icon::handle::from_name("document-save-symbolic") - .size(16) - .handle(), - trailing_icon: button::hyperlink::icon(), - nav_model, - }; - - let command = app.update_title(); - - (app, command) - } - - /// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. - fn nav_model(&self) -> Option<&nav_bar::Model> { - Some(&self.nav_model) - } - - /// Called when a navigation item is selected. - fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { - self.nav_model.activate(id); - self.update_title() - } - - /// Handle application events here. - fn update(&mut self, _message: Self::Message) -> Command { - Command::none() - } - - /// Creates a view after each update. - fn view(&self) -> Element { - let page_content = match self.nav_model.active_data::() { - Some(Page::Buttons) => self.view_buttons(), - None => cosmic::widget::text("Unknown page selected").into(), - }; - - container(page_content) - .width(iced::Length::Fill) - .align_x(iced::alignment::Horizontal::Center) - .apply(scrollable) - .into() - } -} - -impl App -where - Self: cosmic::Application, -{ - fn active_page_title(&mut self) -> &str { - self.nav_model - .text(self.nav_model.active()) - .unwrap_or("Unknown Page") - } - - fn update_title(&mut self) -> Command { - let title = self.active_page_title().to_owned(); - self.set_title(title) - } - - fn view_buttons(&self) -> Element { - column() - .max_width(800) - .spacing(24) - .push(text::title1("Label Buttons")) - // Suggested button header - .push( - column() - .spacing(8) - .push(text::title3("Suggested Button")) - .push(text("Highest level of attention, there should only be one primary button used on the page.").size(14.0)) - ) - // Suggested button demo - .push( - row() - .spacing(36) - .push(button::suggested("Label").on_press(Message::Clicked)) - .push(button::suggested("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) - .push(button::suggested("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) - .push(button::suggested("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) - .push( - button::suggested("Label") - .on_press(Message::Clicked) - .leading_icon(self.app_icon.clone()) - .trailing_icon(self.trailing_icon.clone()) - ) - ) - // Destructive button header - .push( - column() - .spacing(8) - .push(text::title3("Destructive Button")) - .push(text("Highest level of attention, there should only be one primary button used on the page.").size(14.0)) - ) - // Destructive button demo - .push( - row() - .spacing(36) - .push(button::destructive("Label").on_press(Message::Clicked)) - .push(button::destructive("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) - .push(button::destructive("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) - .push(button::destructive("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) - .push( - button::destructive("Label") - .on_press(Message::Clicked) - .leading_icon(self.app_icon.clone()) - .trailing_icon(self.trailing_icon.clone()) - ) - ) - // Standard button header - .push( - column() - .spacing(8) - .push(text::title3("Standard Button")) - .push( - text( - "Requires less attention from the user. Could be more \ - than one button on the page, if necessary." - ) - .size(14.0) - ) - ) - // Standard button demo - .push( - row() - .spacing(36) - .push(button::standard("Label").on_press(Message::Clicked)) - .push(button::standard("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) - .push(button::standard("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) - .push(button::standard("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) - .push( - button::standard("Label") - .on_press(Message::Clicked) - .leading_icon(self.app_icon.clone()) - .trailing_icon(self.trailing_icon.clone()) - ) - ) - // Text button header - .push( - column() - .spacing(8) - .push(text::title3("Text Button")) - .push(text( - "Lowest priority actions, especially when presenting multiple options. Because text buttons \ - don’t have a visible container in their default state, they don’t distract from nearby \ - content. But they are also more difficult to recognize because of that." - ).size(14.0)) - ) - // Text button demo - .push( - row() - .spacing(36) - .push(button::text("Label").on_press(Message::Clicked)) - .push(button::text("Label").on_press(Message::Clicked).leading_icon(self.leading_icon.clone())) - .push(button::text("Label").on_press(Message::Clicked).trailing_icon(self.trailing_icon.clone())) - .push(button::text("Label").on_press(Message::Clicked).leading_icon(self.app_icon.clone())) - .push( - button::text("Label") - .on_press(Message::Clicked) - .leading_icon(self.app_icon.clone()) - .trailing_icon(self.trailing_icon.clone()) - ) - ) - // Icon buttons - .push(text::title1("Icon Buttons")) - // Extra small icon buttons - .push( - row() - .spacing(36) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).extra_small()) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).extra_small().selected(true)) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).extra_small().label("Label")) - .push( - button::icon(self.bt_icon.clone()) - .on_press(Message::Clicked) - .extra_small() - .label("Label") - .selected(true) - ) - ) - // Small (default) icon buttons - .push( - row() - .spacing(36) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked)) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).selected(true)) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).label("Label")) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).label("Label").selected(true)) - ) - // Medium icon buttons - .push( - row() - .spacing(36) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).medium()) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).medium().selected(true)) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).medium().label("Label")) - .push( - button::icon(self.bt_icon.clone()) - .on_press(Message::Clicked) - .medium() - .label("Label") - .selected(true) - ) - ) - // Large icon buttons - .push( - row() - .spacing(36) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).large()) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).large().selected(true)) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).large().label("Label")) - .push( - button::icon(self.bt_icon.clone()) - .on_press(Message::Clicked) - .large() - .label("Label") - .selected(true) - ) - ) - // Extra large icon buttons - .push( - row() - .spacing(36) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).extra_large()) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).extra_large().selected(true)) - .push(button::icon(self.bt_icon.clone()).on_press(Message::Clicked).extra_large().label("Label")) - .push( - button::icon(self.bt_icon.clone()) - .on_press(Message::Clicked) - .extra_large() - .label("Label") - .selected(true) - ) - ) - .into() - } -} diff --git a/justfile b/justfile index 08dd2d48..aaaf3942 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -examples := 'application cosmic cosmic_sctk design open_dialog' +examples := 'application cosmic design open_dialog' # Check for errors and linter warnings check *args: (check-wayland args) (check-winit args) (check-examples args)