diff --git a/crates/app/src/story_workspace.rs b/crates/app/src/story_workspace.rs index 1829ed62..10c310aa 100644 --- a/crates/app/src/story_workspace.rs +++ b/crates/app/src/story_workspace.rs @@ -3,9 +3,9 @@ use prelude::FluentBuilder as _; use story::{ ButtonStory, CheckboxStory, DropdownStory, IconStory, ImageStory, InputStory, ListStory, PickerStory, PopoverStory, ProgressStory, ResizableStory, ScrollableStory, StoryContainer, - SwitchStory, TableStory, TooltipStory, WebViewStory, + SwitchStory, TableStory, TooltipStory, }; -use workspace::{dock::DockPosition, TitleBar, Workspace}; +use workspace::{TitleBar, Workspace}; use std::sync::Arc; use ui::{ @@ -113,13 +113,14 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::add_panel( + StoryContainer::add_pane( + "List", + "A list displays a series of items.", ListStory::view(cx).into(), workspace.clone(), - DockPosition::Left, - px(300.), cx, - ); + ) + .detach(); StoryContainer::add_pane( "Icon", @@ -139,13 +140,13 @@ impl StoryWorkspace { ) .detach(); - StoryContainer::add_panel( - WebViewStory::view(cx).into(), - workspace.clone(), - DockPosition::Right, - px(450.), - cx, - ); + // StoryContainer::add_panel( + // WebViewStory::view(cx).into(), + // workspace.clone(), + // DockPosition::Right, + // px(450.), + // cx, + // ); StoryContainer::add_pane( "Table", diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 56f8978a..ace96f59 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -307,10 +307,6 @@ impl FocusableView for MyPanel { } impl Render for MyPanel { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { - div() - .id("my-panel") - .track_focus(&self.focus_handle) - .size_full() - .child(self.view.clone()) + div().id("my-panel").size_full().child(self.view.clone()) } } diff --git a/crates/story/src/list_story.rs b/crates/story/src/list_story.rs index 7d492ea2..4ddfd6dc 100644 --- a/crates/story/src/list_story.rs +++ b/crates/story/src/list_story.rs @@ -71,11 +71,11 @@ impl RenderOnce for CompanyListItem { }; let bg_color = if self.selected { - cx.theme().accent + cx.theme().list_active } else if self.ix % 2 == 0 { - cx.theme().background + cx.theme().list } else { - cx.theme().accent.opacity(0.3) + cx.theme().list_even }; self.base @@ -266,13 +266,9 @@ impl Render for ListStory { .size_full() .gap_4() .mb_4() - .child( - v_flex() - .h_full() - .w_full() - .border_r_1() - .border_color(cx.theme().border) - .child(self.company_list.clone()), - ) + .border_1() + .border_color(cx.theme().border) + .rounded_md() + .child(v_flex().h_full().w_full().child(self.company_list.clone())) } } diff --git a/crates/story/src/table_story.rs b/crates/story/src/table_story.rs index d9040fc4..136a6d71 100644 --- a/crates/story/src/table_story.rs +++ b/crates/story/src/table_story.rs @@ -1,13 +1,15 @@ use fake::Fake; use gpui::{ - ParentElement, Pixels, Render, SharedString, Styled, View, ViewContext, VisualContext as _, - WindowContext, + div, img, IntoElement, ParentElement, Pixels, Render, SharedString, Styled, View, ViewContext, + VisualContext as _, WindowContext, }; use ui::{ checkbox::Checkbox, h_flex, + label::Label, table::{ColSort, Table, TableDelegate, TableEvent}, - v_flex, Selectable, Selection, + theme::ActiveTheme as _, + v_flex, Icon, IconName, Selectable, Selection, }; struct Customer { @@ -26,6 +28,14 @@ struct Customer { confirmed: bool, } +impl Customer { + fn render_avatar(&self, _: &mut WindowContext) -> impl IntoElement { + let image_id = self.id % 70 + 1; + let avatar_url = format!("https://i.pravatar.cc/40?image={}", image_id); + img(avatar_url).size_5().rounded_full() + } +} + fn randome_customers(size: usize) -> Vec { (0..size) .map(|id| Customer { @@ -38,10 +48,10 @@ fn randome_customers(size: usize) -> Vec { country: fake::faker::address::en::CountryName().fake::(), email: fake::faker::internet::en::FreeEmail().fake::(), phone: fake::faker::phone_number::en::PhoneNumber().fake::(), - gender: (0..1).fake(), + gender: (0..=1).fake(), age: (18..80).fake(), - verified: (0..1).fake::() == 1, - confirmed: (0..1).fake::() == 1, + verified: (0..=1).fake::() == 1, + confirmed: (0..=1).fake::() == 1, }) .collect() } @@ -124,7 +134,7 @@ impl TableDelegate for CustomerTableDelegate { if let Some(col) = self.columns.get(col_ix) { Some( match col.id.as_ref() { - "id" => 50.0, + "id" => 100.0, "login" => 220.0, "first_name" => 150.0, "last_name" => 150.0, @@ -151,29 +161,50 @@ impl TableDelegate for CustomerTableDelegate { return self.col_resize && col_ix > 1; } - fn render_td(&self, row_ix: usize, col_ix: usize) -> impl gpui::IntoElement { + fn render_td(&self, row_ix: usize, col_ix: usize, cx: &mut WindowContext) -> impl IntoElement { let customer = self.customers.get(row_ix).unwrap(); let col = self.columns.get(col_ix).unwrap(); - let text = match col.id.as_ref() { - "id" => customer.id.to_string(), - "login" => customer.login.clone(), - "first_name" => customer.first_name.clone(), - "last_name" => customer.last_name.clone(), - "company" => customer.company.clone(), - "city" => customer.city.clone(), - "country" => customer.country.clone(), - "email" => customer.email.clone(), - "phone" => customer.phone.clone(), - "gender" => customer.gender.to_string(), - "age" => customer.age.to_string(), - "verified" => customer.verified.to_string(), - "confirmed" => customer.confirmed.to_string(), - "twitter" => "twitter".to_string(), - _ => "--".to_string(), - }; - - SharedString::from(text) + match col.id.as_ref() { + "id" => customer.id.to_string().into_any_element(), + "login" => h_flex() + .items_center() + .gap_2() + .child(customer.render_avatar(cx)) + .child(customer.login.clone()) + .into_any_element(), + "first_name" => customer.first_name.clone().into_any_element(), + "last_name" => customer.last_name.clone().into_any_element(), + "company" => h_flex() + .items_center() + .justify_between() + .gap_1() + .child(customer.company.clone()) + .child(IconName::Info) + .into_any_element(), + "city" => customer.city.clone().into_any_element(), + "country" => customer.country.clone().into_any_element(), + "email" => customer.email.clone().into_any_element(), + "phone" => customer.phone.clone().into_any_element(), + "gender" => match customer.gender { + 0 => "Male", + 1 => "Famale", + _ => "", + } + .into_any_element(), + "age" => customer.age.to_string().into_any_element(), + "verified" => match customer.verified { + true => "Yes".to_string().into_any_element(), + false => "No".to_string().into_any_element(), + }, + "confirmed" => match customer.confirmed { + true => Icon::new(IconName::Check).size_4().into_any_element(), + false => div().into_any_element(), + }, + _ => Label::new("--") + .text_color(cx.theme().muted_foreground) + .into_any_element(), + } } fn can_loop_select(&self) -> bool { @@ -190,7 +221,7 @@ impl TableDelegate for CustomerTableDelegate { } fn col_sort(&self, col_ix: usize) -> Option { - self.columns.get(col_ix).map(|c| c.sort).flatten() + self.columns.get(col_ix).and_then(|c| c.sort) } fn perform_sort(&mut self, col_ix: usize, sort: ColSort, _: &mut WindowContext) { @@ -284,7 +315,7 @@ impl TableStory { } fn new(cx: &mut ViewContext) -> Self { - let delegate = CustomerTableDelegate::new(2000); + let delegate = CustomerTableDelegate::new(5000); let table = cx.new_view(|cx| Table::new(delegate, cx)); cx.subscribe(&table, Self::on_table_event).detach(); diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index bcd2e9d8..e8cc6321 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -119,21 +119,21 @@ where self.selected_index } - fn render_item( - &self, - ix: usize, - _cx: &mut gpui::ViewContext>, - ) -> Option { + fn render_item(&self, ix: usize, cx: &mut gpui::ViewContext>) -> Option { let selected = self .selected_index .map_or(false, |selected_index| selected_index == ix); + let size = self + .dropdown + .upgrade() + .map_or(Size::Medium, |dropdown| dropdown.read(cx).size); if let Some(item) = self.delegate.get(ix) { let list_item = ListItem::new(("list-item", ix)) .check_icon(IconName::Check) .selected(selected) - .py_1() - .px_3() + .input_text_size(size) + .list_size(size) .child(item.title().to_string()); Some(list_item) } else { @@ -143,7 +143,8 @@ where fn cancel(&mut self, cx: &mut ViewContext>) { if let Some(view) = self.dropdown.upgrade() { - cx.update_view(&view, |view, _| { + cx.update_view(&view, |view, cx| { + view.focus(cx); view.open = false; }); } @@ -159,6 +160,7 @@ where .and_then(|ix| self.delegate.get(ix)) .map(|item| item.value().clone()); cx.emit(DropdownEvent::Confirm(selected_value.clone())); + view.focus(cx); view.selected_value = selected_value; view.open = false; }); @@ -294,6 +296,10 @@ where self.selected_value.as_ref() } + pub fn focus(&self, cx: &mut WindowContext) { + self.focus_handle.focus(cx); + } + fn up(&mut self, _: &Up, cx: &mut ViewContext) { if !self.open { return; @@ -420,7 +426,7 @@ where D: DropdownDelegate + 'static, { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { - let focused = self.focus_handle.is_focused(cx); + let is_focused = self.focus_handle.is_focused(cx); let show_clean = self.cleanable && self.selected_index(cx).is_some(); div() @@ -436,7 +442,9 @@ where .relative() .child( div() - .id(self.id.clone()) + .id(ElementId::Name( + format!("dropdown-input:{}", self.id).into(), + )) .relative() .flex() .w_full() @@ -447,10 +455,8 @@ where .border_color(cx.theme().input) .rounded(px(cx.theme().radius)) .shadow_sm() - .when(focused, |this| this.outline(cx)) - .input_px(self.size) - .input_py(self.size) - .input_h(self.size) + .when(is_focused, |this| this.outline(cx)) + .input_size(self.size) .when(!self.open, |this| { this.on_click(cx.listener(Self::toggle_menu)) }) diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index be846cd3..cbc26f93 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -3,15 +3,15 @@ use std::{cell::Cell, rc::Rc}; use crate::input::{InputEvent, TextInput}; use crate::scroll::ScrollbarState; -use crate::theme::{ActiveTheme, Colorize}; +use crate::theme::ActiveTheme; use crate::IconName; use crate::{scroll::Scrollbar, v_flex}; -use gpui::SharedString; use gpui::{ actions, div, prelude::FluentBuilder, uniform_list, AppContext, FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton, ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, }; +use gpui::{SharedString, WindowContext}; use smol::Timer; actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]); @@ -135,8 +135,8 @@ where &mut self.delegate } - pub fn focus(&mut self, cx: &mut ViewContext) { - cx.focus(&self.focus_handle); + pub fn focus(&mut self, cx: &mut WindowContext) { + self.focus_handle(cx).focus(cx); } pub fn set_selected_index(&mut self, ix: Option, cx: &mut ViewContext) { @@ -298,7 +298,7 @@ where ListSizingBehavior::Auto }; - let selected_bg = cx.theme().accent.opacity(0.8); + let selected_bg = cx.theme().list_active; v_flex() .key_context("List") diff --git a/crates/ui/src/list/list_item.rs b/crates/ui/src/list/list_item.rs index 5d15f1b2..3b7dc0bb 100644 --- a/crates/ui/src/list/list_item.rs +++ b/crates/ui/src/list/list_item.rs @@ -112,9 +112,9 @@ impl RenderOnce for ListItem { this } }) - .when(self.selected, |this| this.bg(cx.theme().list_item_active)) + .when(self.selected, |this| this.bg(cx.theme().list_active)) .when(!self.selected && !self.disabled, |this| { - this.hover(|this| this.bg(cx.theme().list_item_hover)) + this.hover(|this| this.bg(cx.theme().list_hover)) }) // Right click .when_some(self.on_secondary_mouse_down, |this, on_mouse_down| { diff --git a/crates/ui/src/styled_ext.rs b/crates/ui/src/styled_ext.rs index 5f409b67..2f6b3299 100644 --- a/crates/ui/src/styled_ext.rs +++ b/crates/ui/src/styled_ext.rs @@ -196,15 +196,29 @@ impl From for Size { #[allow(unused)] pub trait StyleSized { + fn input_text_size(self, size: Size) -> Self; fn input_size(self, size: Size) -> Self; fn input_pl(self, size: Size) -> Self; fn input_pr(self, size: Size) -> Self; fn input_px(self, size: Size) -> Self; fn input_py(self, size: Size) -> Self; fn input_h(self, size: Size) -> Self; + fn list_size(self, size: Size) -> Self; + fn list_px(self, size: Size) -> Self; + fn list_py(self, size: Size) -> Self; } impl StyleSized for T { + fn input_text_size(self, size: Size) -> Self { + match size { + Size::XSmall => self.text_size(rems(0.75)), + Size::Small => self.text_size(rems(0.8)), + Size::Medium => self.text_size(rems(0.875)), + Size::Large => self.text_size(rems(1.)), + Size::Size(size) => self.text_size(size), + } + } + fn input_size(self, size: Size) -> Self { self.input_px(size).input_py(size).input_h(size) } @@ -243,9 +257,30 @@ impl StyleSized for T { fn input_h(self, size: Size) -> Self { match size { - Size::Large => self.h_11().text_size(rems(1.)), - Size::Medium => self.h_8().text_size(rems(0.875)), - _ => self.h(px(26.)).text_size(rems(0.8)), + Size::Large => self.h_11(), + Size::Medium => self.h_8(), + _ => self.h(px(26.)), + } + .input_text_size(size) + } + + fn list_size(self, size: Size) -> Self { + self.list_px(size).list_py(size).input_text_size(size) + } + + fn list_px(self, size: Size) -> Self { + match size { + Size::Small => self.px_2(), + _ => self.px_3(), + } + } + + fn list_py(self, size: Size) -> Self { + match size { + Size::Large => self.py_2(), + Size::Medium => self.py_1(), + Size::Small => self.py_0p5(), + _ => self.py_1(), } } } diff --git a/crates/ui/src/table.rs b/crates/ui/src/table.rs index eaa6daaf..a20703f5 100644 --- a/crates/ui/src/table.rs +++ b/crates/ui/src/table.rs @@ -144,12 +144,12 @@ pub trait TableDelegate: Sized + 'static { fn perform_sort(&mut self, col_ix: usize, sort: ColSort, cx: &mut WindowContext) {} /// Render the header cell at the given column index, default to the column name. - fn render_th(&self, col_ix: usize) -> impl IntoElement { + fn render_th(&self, col_ix: usize, cx: &mut WindowContext) -> impl IntoElement { div().size_full().child(self.col_name(col_ix)) } /// Render cell at the given row and column. - fn render_td(&self, row_ix: usize, col_ix: usize) -> impl IntoElement; + fn render_td(&self, row_ix: usize, col_ix: usize, cx: &mut WindowContext) -> impl IntoElement; /// Return true to enable loop selection on the table. /// @@ -558,7 +558,7 @@ where .size_full() .justify_between() .items_center() - .child(self.delegate.render_th(col_ix)) + .child(self.delegate.render_th(col_ix, cx)) .children(self.render_sort_icon(col_ix, cx)), ) .when(self.delegate.can_move_col(col_ix), |this| { @@ -703,12 +703,14 @@ where tr(cx) .id(("table-row", row_ix)) .w_full() - .when(row_ix > 0, |this| this.border_t_1()) - .when(row_ix % 2 == 0, |this| { + .when(row_ix > 0, |this| { + this.border_t_1().border_color(cx.theme().border) + }) + .when(row_ix % 2 != 0, |this| { this.bg(cx.theme().table_even) }) .hover(|this| { - if table.selected_row.is_some() { + if table.selected_row == Some(row_ix) { this } else { this.bg(cx.theme().table_hover) @@ -725,7 +727,7 @@ where .child( table .delegate - .render_td(row_ix, col_ix), + .render_td(row_ix, col_ix, cx), ), ) })) diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index 554dbc50..c39bd8bb 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -157,6 +157,10 @@ struct Colors { pub scrollbar_thumb: Hsla, pub panel: Hsla, pub tab_bar: Hsla, + pub list: Hsla, + pub list_even: Hsla, + pub list_active: Hsla, + pub list_head: Hsla, } impl Colors { @@ -193,10 +197,10 @@ impl Colors { card_foreground: hsl(240.0, 10.0, 3.9), popover: hsl(0.0, 0.0, 100.0), popover_foreground: hsl(240.0, 10.0, 3.9), - primary: hsl(240.0, 5.9, 10.0), - primary_hover: hsl(240.0, 5.9, 30.0), - primary_active: hsl(240.0, 5.9, 45.0), - primary_foreground: hsl(0.0, 0.0, 98.0), + primary: hsl(223.0, 5.9, 10.0), + primary_hover: hsl(223.0, 5.9, 30.0), + primary_active: hsl(223.0, 5.9, 45.0), + primary_foreground: hsl(223.0, 0.0, 98.0), secondary: hsl(240.0, 4.8, 95.9), secondary_hover: hsl(240.0, 4.8, 99.), secondary_active: hsl(240.0, 5.9, 94.0), @@ -217,6 +221,10 @@ impl Colors { scrollbar_thumb: hsl(0., 0., 49.), panel: hsl(0.0, 0.0, 100.0), tab_bar: hsl(240.0, 4.8, 95.9), + list: hsl(0.0, 0.0, 100.), + list_even: hsl(240.0, 5.0, 96.0), + list_active: hsl(240.0, 7., 88.0), + list_head: hsl(240.0, 0., 94.), } } @@ -252,10 +260,10 @@ impl Colors { card_foreground: hsl(0.0, 0.0, 98.0), popover: hsl(240.0, 10.0, 3.9), popover_foreground: hsl(0.0, 0.0, 98.0), - primary: hsl(0.0, 0.0, 98.0), - primary_hover: hsl(0.0, 0.0, 85.0), - primary_active: hsl(0.0, 0.0, 60.0), - primary_foreground: hsl(240.0, 5.9, 10.0), + primary: hsl(223.0, 0.0, 98.0), + primary_hover: hsl(223.0, 0.0, 85.0), + primary_active: hsl(223.0, 0.0, 60.0), + primary_foreground: hsl(223.0, 5.9, 10.0), secondary: hsl(240.0, 3.7, 15.9), secondary_hover: hsl(240.0, 3.7, 20.9), secondary_active: hsl(240.0, 3.7, 8.9), @@ -276,6 +284,10 @@ impl Colors { scrollbar_thumb: hsl(0., 0., 58.), panel: hsl(299.0, 2., 9.), tab_bar: hsl(299.0, 2., 9.), + list: hsl(0.0, 0.0, 6.0), + list_even: hsl(240.0, 3.7, 8.0), + list_active: hsl(240.0, 3.7, 15.0), + list_head: hsl(240.0, 3.7, 10.9), } } } @@ -329,8 +341,11 @@ pub struct Theme { pub progress_bar: Hsla, pub slider_bar: Hsla, pub slider_thumb: Hsla, - pub list_item_active: Hsla, - pub list_item_hover: Hsla, + pub list: Hsla, + pub list_even: Hsla, + pub list_head: Hsla, + pub list_active: Hsla, + pub list_hover: Hsla, pub table: Hsla, pub table_even: Hsla, pub table_head: Hsla, @@ -401,13 +416,16 @@ impl From for Theme { progress_bar: colors.primary, slider_bar: colors.primary, slider_thumb: colors.background, - list_item_active: colors.secondary_active, - list_item_hover: colors.secondary, - table_head: colors.secondary.opacity(0.5), - table: colors.background, - table_even: colors.secondary.opacity(0.3), - table_active: colors.secondary_active, - table_hover: colors.secondary_active.opacity(0.7), + list: colors.list, + list_even: colors.list_even, + list_head: colors.list_head, + list_active: colors.list_active, + list_hover: colors.list_active.opacity(0.6), + table_head: colors.list_head, + table: colors.list, + table_even: colors.list_even, + table_active: colors.list_active, + table_hover: colors.list_active.opacity(0.6), } } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 8c05631e..310597cf 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -200,8 +200,6 @@ impl Dock { let Some(_panel) = dock.read(cx).active_panel() else { return; }; - - // workspace.update_active_view_for_followers(cx) } }) .detach(); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f051a7b7..1c74bbf3 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -669,8 +669,8 @@ impl Pane { .suffix( div() .id("close-tab") - .p(px(0.5)) - .rounded_lg() + .p(px(0.)) + .rounded_sm() .invisible() .child(Icon::new(IconName::Close).size(px(12.))) .hover(|this| this.bg(cx.theme().accent.darken(0.1)))