diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index 80bb143d8b..b9d322a3c0 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -2,12 +2,13 @@ use std::{cell::RefCell, fmt::Write as _, rc::Rc}; use db::anyhow::anyhow; use gpui::{ - AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Keymap, - Subscription, Task, actions, + AnyElement, AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, + Global, IntoElement, Keymap, Length, Subscription, Task, actions, div, }; + use ui::{ - ActiveTheme as _, App, BorrowAppContext, ParentElement as _, Render, SharedString, Styled as _, - Table, Window, div, string_cell, + ActiveTheme as _, App, BorrowAppContext, Indicator, ParentElement as _, Render, SharedString, + Styled as _, Window, prelude::*, }; use workspace::{Item, SerializableItem, Workspace, register_serializable_item}; @@ -30,10 +31,6 @@ pub fn init(cx: &mut App) { register_serializable_item::(cx); } -enum KeymapEvent { - KeymapChanged, -} - pub struct KeymapEventChannel {} impl EventEmitter for KeymapEventChannel {} @@ -212,6 +209,156 @@ impl Render for KeymapEditor { } } +/// A table component +#[derive(IntoElement)] +pub struct Table { + column_headers: Vec, + rows: Vec>, + column_count: usize, + striped: bool, + width: Length, +} + +impl Table { + /// Create a new table with a column count equal to the + /// number of headers provided. + pub fn new(headers: Vec>) -> Self { + let column_count = headers.len(); + + Table { + column_headers: headers.into_iter().map(Into::into).collect(), + column_count, + rows: Vec::new(), + striped: false, + width: Length::Auto, + } + } + + /// Adds a row to the table. + /// + /// The row must have the same number of columns as the table. + pub fn row(mut self, items: Vec>) -> Self { + if items.len() == self.column_count { + self.rows.push(items.into_iter().map(Into::into).collect()); + } else { + log::error!("Row length mismatch"); + } + self + } + + /// Adds multiple rows to the table. + /// + /// Each row must have the same number of columns as the table. + /// Rows that don't match the column count are ignored. + pub fn rows(mut self, rows: Vec>>) -> Self { + for row in rows { + self = self.row(row); + } + self + } + + fn base_cell_style(cx: &mut App) -> Div { + div() + .px_1p5() + .flex_1() + .justify_start() + .text_ui(cx) + .whitespace_nowrap() + .text_ellipsis() + .overflow_hidden() + } + + /// Enables row striping. + pub fn striped(mut self) -> Self { + self.striped = true; + self + } + + /// Sets the width of the table. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } +} + +impl RenderOnce for Table { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + let header = div() + .flex() + .flex_row() + .items_center() + .justify_between() + .w_full() + .p_2() + .border_b_1() + .border_color(cx.theme().colors().border) + .children(self.column_headers.into_iter().map(|h| { + Table::base_cell_style(cx) + .font_weight(FontWeight::SEMIBOLD) + .child(h.clone()) + })); + + let row_count = self.rows.len(); + let rows = self.rows.into_iter().enumerate().map(|(ix, row)| { + let is_last = ix == row_count - 1; + let bg = if ix % 2 == 1 && self.striped { + Some(cx.theme().colors().text.opacity(0.05)) + } else { + None + }; + div() + .w_full() + .flex() + .flex_row() + .items_center() + .justify_between() + .px_1p5() + .py_1() + .when_some(bg, |row, bg| row.bg(bg)) + .when(!is_last, |row| { + row.border_b_1().border_color(cx.theme().colors().border) + }) + .children(row.into_iter().map(|cell| match cell { + TableCell::String(s) => Self::base_cell_style(cx).child(s), + TableCell::Element(e) => Self::base_cell_style(cx).child(e), + })) + }); + + div() + .w(self.width) + .overflow_hidden() + .child(header) + .children(rows) + } +} + +/// Represents a cell in a table. +pub enum TableCell { + /// A cell containing a string value. + String(SharedString), + /// A cell containing a UI element. + Element(AnyElement), +} + +/// Creates a `TableCell` containing a string value. +pub fn string_cell(s: impl Into) -> TableCell { + TableCell::String(s.into()) +} + +/// Creates a `TableCell` containing an element. +pub fn element_cell(e: impl Into) -> TableCell { + TableCell::Element(e.into()) +} + +impl From for TableCell +where + E: Into, +{ + fn from(e: E) -> Self { + TableCell::String(e.into()) + } +} + mod persistence { use db::{define_connection, query, sqlez_macros::sql}; use workspace::WorkspaceDb; diff --git a/crates/ui/src/components/table.rs b/crates/ui/src/components/table.rs index 931660ea87..bd8fc140fb 100644 --- a/crates/ui/src/components/table.rs +++ b/crates/ui/src/components/table.rs @@ -85,9 +85,9 @@ impl RenderOnce for Table { .border_b_1() .border_color(cx.theme().colors().border) .children(self.column_headers.into_iter().map(|h| { - Self::base_cell_style(cx) + Table::base_cell_style(cx) .font_weight(FontWeight::SEMIBOLD) - .child(h) + .child(h.clone()) })); let row_count = self.rows.len();