diff --git a/examples/table-view/Cargo.toml b/examples/table-view/Cargo.toml new file mode 100644 index 00000000..ba3bd88e --- /dev/null +++ b/examples/table-view/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "table-view" +version = "0.1.0" +edition = "2021" + +[dependencies] +tracing = "0.1.37" +tracing-subscriber = "0.3.17" +tracing-log = "0.2.0" +chrono = "*" + +[dependencies.libcosmic] +features = ["debug", "multi-window", "wayland", "winit", "desktop", "tokio"] +path = "../.." +default-features = false diff --git a/examples/table-view/src/main.rs b/examples/table-view/src/main.rs new file mode 100644 index 00000000..b7cbb8ab --- /dev/null +++ b/examples/table-view/src/main.rs @@ -0,0 +1,200 @@ +// Copyright 2023 System76 +// SPDX-License-Identifier: MPL-2.0 + +//! Table API example + +use chrono::Datelike; +use cosmic::app::{Core, Settings, Task}; +use cosmic::iced_core::Size; +use cosmic::widget::nav_bar; +use cosmic::widget::table; +use cosmic::{executor, iced, Element}; + +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] +pub enum Category { + #[default] + Name, + Date, + Size, +} + +impl std::fmt::Display for Category { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Self::Name => "Name", + Self::Date => "Date", + Self::Size => "Size", + }) + } +} + +impl table::model::category::ItemCategory for Category { + fn width(&self) -> iced::Length { + match self { + Self::Name => iced::Length::Fill, + Self::Date => iced::Length::Fixed(200.0), + Self::Size => iced::Length::Fixed(150.0), + } + } +} + +struct Item { + name: String, + date: chrono::DateTime, + size: u64, +} + +impl Default for Item { + fn default() -> Self { + Self { + name: Default::default(), + date: Default::default(), + size: Default::default(), + } + } +} + +impl table::model::category::ItemInterface for Item { + fn get_icon(&self, category: Category) -> Option { + if category == Category::Name { + Some(cosmic::widget::icon::from_name("application-x-executable-symbolic").icon()) + } else { + None + } + } + + fn get_text(&self, category: Category) -> std::borrow::Cow<'static, str> { + match category { + Category::Name => self.name.clone().into(), + Category::Date => self.date.format("%Y/%m/%d").to_string().into(), + Category::Size => format!("{} items", self.size).into(), + } + } + + fn compare(&self, other: &Self, category: Category) -> std::cmp::Ordering { + match category { + Category::Name => self.name.to_lowercase().cmp(&other.name.to_lowercase()), + Category::Date => self.date.cmp(&other.date), + Category::Size => self.size.cmp(&other.size), + } + } +} + +/// Runs application with these settings +#[rustfmt::skip] +fn main() -> Result<(), Box> { + tracing_subscriber::fmt::init(); + let _ = tracing_log::LogTracer::init(); + + let settings = Settings::default() + .size(Size::new(1024., 768.)); + + cosmic::app::run::(settings, ())?; + + Ok(()) +} + +/// Messages that are used specifically by our [`App`]. +#[derive(Clone, Debug)] +pub enum Message { + ItemSelect(table::Entity), + CategorySelect(Category, bool), +} + +/// The [`App`] stores application-specific state. +pub struct App { + core: Core, + table_model: table::Model, +} + +/// 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 = "org.cosmic.AppDemoTable"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + /// Creates the application, and optionally emits task on initialize. + fn init(core: Core, _: Self::Flags) -> (Self, Task) { + let mut nav_model = nav_bar::Model::default(); + + nav_model.activate_position(0); + + let mut table_model = + table::Model::new(vec![Category::Name, Category::Date, Category::Size]); + + table_model.insert().item(Item { + name: "Foo".into(), + date: chrono::DateTime::default() + .with_day(1) + .unwrap() + .with_month(1) + .unwrap() + .with_year(1970) + .unwrap(), + size: 2, + }); + table_model.insert().item(Item { + name: "Bar".into(), + date: chrono::DateTime::default() + .with_day(2) + .unwrap() + .with_month(1) + .unwrap() + .with_year(1970) + .unwrap(), + size: 4, + }); + table_model.insert().item(Item { + name: "Baz".into(), + date: chrono::DateTime::default() + .with_day(3) + .unwrap() + .with_month(1) + .unwrap() + .with_year(1970) + .unwrap(), + size: 12, + }); + + let app = App { core, table_model }; + + let command = Task::none(); + + (app, command) + } + + /// Handle application events here. + fn update(&mut self, message: Self::Message) -> Task { + match message { + Message::ItemSelect(entity) => self.table_model.activate(entity), + Message::CategorySelect(category, descending) => { + self.table_model.sort(category, descending) + } + } + Task::none() + } + + /// Creates a view after each update. + fn view(&self) -> Element { + table::TableView::new(&self.table_model) + .on_item_select(Message::ItemSelect) + .on_category_select(Message::CategorySelect) + .element_standard() + } +} diff --git a/src/widget/table/widget/mod.rs b/src/widget/table/widget/mod.rs index 02f62e3d..0cb826a4 100644 --- a/src/widget/table/widget/mod.rs +++ b/src/widget/table/widget/mod.rs @@ -215,6 +215,7 @@ where .iter() .map(|category| { widget::row() + .spacing(space_xxxs) .push_maybe(item.get_icon(*category).map(|icon| icon.size(24))) .push(widget::text::body(item.get_text(*category))) .align_y(Alignment::Center)