use std::{cell::RefCell, rc::Rc}; use apply::Apply; use cosmic::{ cosmic_theme, iced::widget::{checkbox, column, progress_bar, radio, slider, text}, iced::{Alignment, Length}, iced_core::id, theme::ThemeType, widget::{ button, color_picker::ColorPickerUpdate, dropdown, icon, layer_container as container, segmented_button, segmented_control, settings, spin_button, tab_bar, toggler, ColorPickerModel, }, Element, }; use cosmic_time::{anim, chain, Timeline}; use fraction::{Decimal, ToPrimitive}; use once_cell::sync::Lazy; use super::{Page, Window}; static CARDS: Lazy = Lazy::new(cosmic_time::id::Cards::unique); #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq)] pub enum ThemeVariant { Light, Dark, HighContrastDark, HighContrastLight, Custom, 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) } } pub enum DemoView { TabA, TabB, TabC, } #[allow(dead_code)] pub enum MultiOption { OptionA, OptionB, OptionC, OptionD, OptionE, } static INPUT_ID: Lazy = Lazy::new(id::Id::unique); #[derive(Clone, Debug)] pub enum Message { ButtonPressed, CheckboxToggled(bool), Debug(bool), IconTheme(segmented_button::Entity), MultiSelection(segmented_button::Entity), DropdownSelect(usize), RowSelected(usize), ScalingFactor(spin_button::Message), Selection(segmented_button::Entity), SliderChanged(f32), SpinButton(spin_button::Message), ThemeChanged(ThemeVariant), ToggleWarning, TogglerToggled(bool), ViewSwitcher(segmented_button::Entity), InputChanged(String), DeleteCard(usize), ClearAll, CardsToggled(bool), ColorPickerUpdate(ColorPickerUpdate), Hidden, } pub enum Output { Debug(bool), ScalingFactor(f32), ThemeChanged(ThemeVariant), ToggleWarning, } pub struct State { pub checkbox_value: bool, pub icon_themes: segmented_button::SingleSelectModel, pub multi_selection: segmented_button::MultiSelectModel, pub dropdown_selected: Option, pub dropdown_options: Vec<&'static str>, pub scaling_value: spin_button::Model, pub selection: segmented_button::SingleSelectModel, pub slider_value: f32, pub spin_button: spin_button::Model, pub toggler_value: bool, pub tab_bar: segmented_button::SingleSelectModel, pub entry_value: String, pub cards_value: bool, cards: Vec, pub timeline: Rc>, pub color_picker_model: ColorPickerModel, pub hidden: bool, } impl Default for State { fn default() -> State { State { checkbox_value: false, dropdown_selected: Some(0), dropdown_options: vec!["Option 1", "Option 2", "Option 3", "Option 4"], scaling_value: spin_button::Model::default() .value(1.0) .min(0.5) .max(2.0) .step(0.25), slider_value: 50.0, spin_button: spin_button::Model::default().min(-10).max(10), toggler_value: false, icon_themes: segmented_button::Model::builder() .insert(|b| b.text("Pop").activate()) .insert(|b| b.text("Adwaita")) .build(), 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(), multi_selection: segmented_button::Model::builder() .insert(|b| b.text("Option A").data(MultiOption::OptionA).activate()) .insert(|b| b.text("Option B").data(MultiOption::OptionB)) .insert(|b| b.text("Option C").data(MultiOption::OptionC)) .insert(|b| b.text("Option D").data(MultiOption::OptionD)) .insert(|b| b.text("Option E").data(MultiOption::OptionE)) .build(), tab_bar: segmented_button::Model::builder() .insert(|b| b.text("Controls").data(DemoView::TabA).activate()) .insert(|b| b.text("Segmented Button").data(DemoView::TabB)) .insert(|b| b.text("Tab C").data(DemoView::TabC)) .build(), cards_value: false, entry_value: String::new(), cards: vec![ "card 1".to_string(), "card 2".to_string(), "card 3".to_string(), "card 4".to_string(), ], timeline: Rc::new(RefCell::new(Default::default())), color_picker_model: ColorPickerModel::new("Hex", "RGB", None, None), hidden: false, } } } impl State { pub(super) fn update(&mut self, message: Message) -> Option { match message { Message::ButtonPressed => (), Message::CheckboxToggled(value) => self.checkbox_value = value, Message::Debug(value) => return Some(Output::Debug(value)), Message::DropdownSelect(value) => self.dropdown_selected = Some(value), Message::RowSelected(row) => println!("Selected row {row}"), Message::MultiSelection(key) => self.multi_selection.activate(key), Message::ScalingFactor(message) => { self.scaling_value.update(message); if let Some(factor) = self.scaling_value.value.to_f32() { return Some(Output::ScalingFactor(factor)); } } Message::Selection(key) => self.selection.activate(key), Message::SliderChanged(value) => self.slider_value = value, Message::SpinButton(msg) => self.spin_button.update(msg), Message::ThemeChanged(theme) => return Some(Output::ThemeChanged(theme)), Message::ToggleWarning => return Some(Output::ToggleWarning), Message::TogglerToggled(value) => self.toggler_value = value, Message::ViewSwitcher(key) => self.tab_bar.activate(key), Message::IconTheme(key) => { self.icon_themes.activate(key); if let Some(theme) = self.icon_themes.text(key) { cosmic::icon_theme::set_default(theme.to_owned()); } } Message::InputChanged(s) => { self.entry_value = s; } Message::ClearAll => { self.cards.clear(); } Message::CardsToggled(v) => { self.cards_value = v; self.update_cards(); } Message::DeleteCard(i) => { self.cards.remove(i); } Message::ColorPickerUpdate(u) => { _ = self.color_picker_model.update::(u); } Message::Hidden => { self.hidden = !self.hidden; } } None } pub(super) fn view<'a>(&'a self, window: &'a Window) -> Element<'a, Message> { let choose_theme = [ ThemeVariant::Light, ThemeVariant::Dark, ThemeVariant::HighContrastLight, ThemeVariant::HighContrastDark, ThemeVariant::Custom, ThemeVariant::System, ] .into_iter() .fold( column![].spacing(10).align_items(Alignment::Center), |row, theme| { row.push(radio( format!("{:?}", theme), theme, if ThemeVariant::from(&window.theme.theme_type) == theme { Some(theme) } else { None }, Message::ThemeChanged, )) }, ); let choose_icon_theme = segmented_control::horizontal(&self.icon_themes).on_activate(Message::IconTheme); let timeline = self.timeline.borrow(); settings::view_column(vec![ window.page_title(Page::Demo), tab_bar::horizontal(&self.tab_bar) .on_activate(Message::ViewSwitcher) .into(), match self.tab_bar.active_data() { None => panic!("no tab is active"), Some(DemoView::TabA) => settings::view_column(vec![ settings::section() .title("Debug") .add(settings::item("Debug theme", choose_theme)) .add(settings::item("Debug icon theme", choose_icon_theme)) .add(settings::item( "Debug layout", toggler(window.debug).on_toggle(Message::Debug), )) .add(settings::item( "Scaling Factor", spin_button(&window.scale_factor_string, Message::ScalingFactor), )) .add(settings::item_row(vec![ cosmic::widget::button::destructive("Do not Touch") .trailing_icon(icon::from_name("dialog-warning-symbolic").size(16)) .on_press(Message::ToggleWarning) .into(), ])) .into(), settings::section() .title("Controls") .add(settings::item( "Toggler", toggler(self.toggler_value).on_toggle(Message::TogglerToggled), )) .add(settings::item( "Pick List (TODO)", dropdown( &self.dropdown_options, self.dropdown_selected, Message::DropdownSelect, ) .padding([8, 0, 8, 16]), )) .add(settings::item( "Slider", slider(0.0..=100.0, self.slider_value, Message::SliderChanged) .width(Length::Fixed(250.0)) .height(38), )) .add(settings::item( "Progress", progress_bar(0.0..=100.0, self.slider_value) .length(Length::Fixed(250.0)) .girth(Length::Fixed(4.0)), )) .add(settings::item_row(vec![checkbox(self.checkbox_value) .label("Checkbox") .on_toggle(Message::CheckboxToggled) .into()])) .add(settings::item( format!( "Spin Button (Range {}:{})", self.spin_button.min, self.spin_button.max ), spin_button(format!("{}", self.spin_button.value), Message::SpinButton), )) .into(), ]) .padding(0) .into(), Some(DemoView::TabB) => settings::view_column(vec![ text("Selection").font(cosmic::font::semibold()).into(), text("Horizontal").into(), segmented_control::horizontal(&self.selection) .on_activate(Message::Selection) .into(), text("Horizontal With Spacing").into(), segmented_control::horizontal(&self.selection) .spacing(8) .on_activate(Message::Selection) .into(), text("Disabled Horizontal With Spacing").into(), segmented_control::horizontal(&self.selection) .spacing(8) .into(), text("Horizontal Multi-Select").into(), segmented_control::horizontal(&self.multi_selection) .spacing(8) .on_activate(Message::MultiSelection) .into(), text("Disabled Horizontal Multi-Select").into(), segmented_control::horizontal(&self.multi_selection) .spacing(8) .into(), text("Vertical").into(), segmented_control::vertical(&self.selection) .on_activate(Message::Selection) .into(), text("Disabled Vertical").into(), segmented_control::vertical(&self.selection).into(), text("Vertical Multi-Select Shrunk").into(), segmented_control::vertical(&self.multi_selection) .width(Length::Shrink) .on_activate(Message::MultiSelection) .apply(container) .center_x(Length::Fill) .into(), text("Vertical With Spacing").into(), cosmic::iced::widget::row(vec![ segmented_control::vertical(&self.selection) .spacing(8) .on_activate(Message::Selection) .width(Length::FillPortion(1)) .into(), segmented_control::vertical(&self.selection) .spacing(8) .on_activate(Message::Selection) .width(Length::FillPortion(1)) .into(), segmented_control::vertical(&self.selection) .spacing(8) .on_activate(Message::Selection) .width(Length::FillPortion(1)) .into(), ]) .spacing(12) .width(Length::Fill) .into(), text("View Switcher").font(cosmic::font::semibold()).into(), text("Horizontal").into(), tab_bar::horizontal(&self.selection) .on_activate(Message::Selection) .into(), text("Horizontal Multi-Select").into(), tab_bar::horizontal(&self.multi_selection) .on_activate(Message::MultiSelection) .into(), text("Horizontal With Spacing").into(), tab_bar::horizontal(&self.selection) .spacing(8) .on_activate(Message::Selection) .into(), text("Vertical").into(), tab_bar::vertical(&self.selection) .on_activate(Message::Selection) .into(), text("Vertical Multi-Select").into(), tab_bar::vertical(&self.multi_selection) .on_activate(Message::MultiSelection) .into(), text("Vertical With Spacing").into(), cosmic::iced::widget::row(vec![ tab_bar::vertical(&self.selection) .spacing(8) .on_activate(Message::Selection) .width(Length::FillPortion(1)) .into(), tab_bar::vertical(&self.selection) .spacing(8) .on_activate(Message::Selection) .width(Length::FillPortion(1)) .into(), tab_bar::vertical(&self.selection) .spacing(8) .on_activate(Message::Selection) .width(Length::FillPortion(1)) .into(), ]) .spacing(12) .width(Length::Fill) .into(), ]) .padding(0) .into(), Some(DemoView::TabC) => settings::view_column(vec![settings::section() .title("Tab C") .add(text("Nothing here yet").width(Length::Fill)) .into()]) .padding(0) .into(), }, container(text("Background container with some text").size(24)) .layer(cosmic_theme::Layer::Background) .padding(8) .width(Length::Fill) .into(), container(column![ text( "Primary container with some text and a couple icons testing default fallbacks" ) .size(24), icon::from_name("microphone-sensitivity-high-symbolic-test") .size(24) .icon(), icon::from_name("microphone-sensitivity-high-symbolic-test") .size(24) .fallback(None) .icon(), ]) .layer(cosmic_theme::Layer::Primary) .padding(8) .width(Length::Fill) .into(), container(text("Secondary container with some text").size(24)) .layer(cosmic_theme::Layer::Secondary) .padding(8) .width(Length::Fill) .into(), container(anim!( //cards CARDS, &timeline, self.cards .iter() .enumerate() .map(|(i, c)| column![ button::text("Delete me").on_press(Message::DeleteCard(i)), text(c).size(24).width(Length::Fill) ] .into()) .collect(), Message::ClearAll, |_, e| Message::CardsToggled(e), "Show More", "Show Less", "Clear All", None, self.cards_value, )) .layer(cosmic::cosmic_theme::Layer::Secondary) .padding(16) .class(cosmic::theme::Container::Background) .into(), cosmic::widget::text_input::secure_input( "Type to search apps or type “?” for more options...", &self.entry_value, Some(Message::Hidden), self.hidden, ) .on_input(Message::InputChanged) .size(20) .id(INPUT_ID.clone()) .into(), cosmic::widget::text_input("", &self.entry_value) .label("Test Input") .helper_text("test helper text") .on_input(Message::InputChanged) .size(20) .id(INPUT_ID.clone()) .into(), self.color_picker_model .picker_button(Message::ColorPickerUpdate, None) .width(Length::Fixed(128.0)) .height(Length::Fixed(128.0)) .into(), if self.color_picker_model.get_is_active() { self.color_picker_model .builder(Message::ColorPickerUpdate) .reset_label("Reset to default") .save_label("Save") .cancel_label("Cancel") .build("Recent Colors", "Copy to clipboard", "Copied to clipboard") .into() } else { text("The color picker is not active.").into() }, ]) .into() } fn update_cards(&mut self) { let mut timeline = self.timeline.borrow_mut(); 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(); } }