theme: Fix list, table theme colors. (#79)

- Fix list, table theme colors.
- Fix table hover bg.
- Add avatar to table list.
- Update dropdown Size:Small style.
This commit is contained in:
Jason Lee 2024-07-29 13:52:45 +08:00 committed by GitHub
parent 01aa9401c5
commit a3df086e79
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 193 additions and 110 deletions

View file

@ -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",

View file

@ -307,10 +307,6 @@ impl FocusableView for MyPanel {
}
impl Render for MyPanel {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> 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())
}
}

View file

@ -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()))
}
}

View file

@ -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<Customer> {
(0..size)
.map(|id| Customer {
@ -38,10 +48,10 @@ fn randome_customers(size: usize) -> Vec<Customer> {
country: fake::faker::address::en::CountryName().fake::<String>(),
email: fake::faker::internet::en::FreeEmail().fake::<String>(),
phone: fake::faker::phone_number::en::PhoneNumber().fake::<String>(),
gender: (0..1).fake(),
gender: (0..=1).fake(),
age: (18..80).fake(),
verified: (0..1).fake::<u8>() == 1,
confirmed: (0..1).fake::<u8>() == 1,
verified: (0..=1).fake::<u8>() == 1,
confirmed: (0..=1).fake::<u8>() == 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<ColSort> {
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>) -> 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();

View file

@ -119,21 +119,21 @@ where
self.selected_index
}
fn render_item(
&self,
ix: usize,
_cx: &mut gpui::ViewContext<List<Self>>,
) -> Option<Self::Item> {
fn render_item(&self, ix: usize, cx: &mut gpui::ViewContext<List<Self>>) -> Option<Self::Item> {
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<List<Self>>) {
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<Self>) {
if !self.open {
return;
@ -420,7 +426,7 @@ where
D: DropdownDelegate + 'static,
{
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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))
})

View file

@ -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<Self>) {
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<usize>, cx: &mut ViewContext<Self>) {
@ -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")

View file

@ -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| {

View file

@ -196,15 +196,29 @@ impl From<Pixels> for Size {
#[allow(unused)]
pub trait StyleSized<T: Styled> {
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<T: Styled> StyleSized<T> 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<T: Styled> StyleSized<T> 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(),
}
}
}

View file

@ -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),
),
)
}))

View file

@ -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<Colors> 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),
}
}
}

View file

@ -200,8 +200,6 @@ impl Dock {
let Some(_panel) = dock.read(cx).active_panel() else {
return;
};
// workspace.update_active_view_for_followers(cx)
}
})
.detach();

View file

@ -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)))