feat: improve compose modal

This commit is contained in:
2025-01-20 15:49:21 +07:00
parent 8f6bedf70a
commit 5f6ba4f0a6
20 changed files with 245 additions and 117 deletions

View File

@@ -126,7 +126,7 @@ impl ChatRegistry {
let id = room_hash(&ev.tags); let id = room_hash(&ev.tags);
// Filter all seen events // Filter all seen events
if !hashes.iter().any(|h| h == &id) { if !hashes.iter().any(|h| h == &id) {
Some(cx.new_model(|_| Room::new(&ev))) Some(cx.new_model(|_| Room::parse(&ev)))
} else { } else {
None None
} }
@@ -170,7 +170,7 @@ impl ChatRegistry {
cx.notify(); cx.notify();
}) })
} else { } else {
let room = cx.new_model(|_| Room::new(&event)); let room = cx.new_model(|_| Room::parse(&event));
self.inbox.update(cx, |this, cx| { self.inbox.update(cx, |this, cx| {
this.rooms.insert(0, room); this.rooms.insert(0, room);

View File

@@ -68,7 +68,27 @@ pub struct Room {
} }
impl Room { impl Room {
pub fn new(event: &Event) -> Self { pub fn new(
id: u64,
owner: Member,
members: Vec<Member>,
title: Option<SharedString>,
last_seen: Timestamp,
) -> Self {
let is_group = members.len() > 1;
Self {
id,
owner,
members,
title,
last_seen,
is_group,
new_messages: vec![],
}
}
pub fn parse(event: &Event) -> Room {
let id = room_hash(&event.tags); let id = room_hash(&event.tags);
let last_seen = event.created_at; let last_seen = event.created_at;
@@ -89,17 +109,7 @@ impl Room {
Some(name.into()) Some(name.into())
}; };
let is_group = members.len() > 1; Self::new(id, owner, members, title, last_seen)
Self {
id,
owner,
members,
title,
last_seen,
is_group,
new_messages: vec![],
}
} }
pub fn set_metadata(&mut self, public_key: PublicKey, metadata: Metadata) { pub fn set_metadata(&mut self, public_key: PublicKey, metadata: Metadata) {

View File

@@ -501,7 +501,7 @@ impl Render for ChatPanel {
div() div()
.flex_1() .flex_1()
.flex() .flex()
.bg(cx.theme().base.step(cx, ColorScaleStep::FOUR)) .bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.px_2() .px_2()
.child(self.input.clone()), .child(self.input.clone()),

View File

@@ -6,13 +6,14 @@ use gpui::{
}; };
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashSet; use std::{collections::HashSet, time::Duration};
use ui::{ use ui::{
button::{Button, ButtonRounded},
indicator::Indicator, indicator::Indicator,
input::TextInput, input::{InputEvent, TextInput},
prelude::FluentBuilder, prelude::FluentBuilder,
theme::{scale::ColorScaleStep, ActiveTheme}, theme::{scale::ColorScaleStep, ActiveTheme},
Icon, IconName, Sizable, StyledExt, Icon, IconName, Sizable, Size, StyledExt,
}; };
#[derive(Clone, PartialEq, Eq, Deserialize)] #[derive(Clone, PartialEq, Eq, Deserialize)]
@@ -21,24 +22,48 @@ struct SelectContact(PublicKey);
impl_internal_actions!(contacts, [SelectContact]); impl_internal_actions!(contacts, [SelectContact]);
pub struct Compose { pub struct Compose {
input: View<TextInput>, title_input: View<TextInput>,
message_input: View<TextInput>,
user_input: View<TextInput>,
contacts: Model<Option<Vec<Member>>>, contacts: Model<Option<Vec<Member>>>,
selected: Model<HashSet<PublicKey>>, selected: Model<HashSet<PublicKey>>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
is_loading: bool,
} }
impl Compose { impl Compose {
pub fn new(cx: &mut ViewContext<'_, Self>) -> Self { pub fn new(cx: &mut ViewContext<'_, Self>) -> Self {
let contacts = cx.new_model(|_| None); let contacts = cx.new_model(|_| None);
let selected = cx.new_model(|_| HashSet::new()); let selected = cx.new_model(|_| HashSet::new());
let input = cx.new_view(|cx| {
let user_input = cx.new_view(|cx| {
TextInput::new(cx)
.text_size(ui::Size::Small)
.small()
.placeholder("npub1...")
});
let title_input = cx.new_view(|cx| {
TextInput::new(cx) TextInput::new(cx)
.appearance(false) .appearance(false)
.text_size(ui::Size::Small) .text_size(Size::XSmall)
.placeholder("npub1...") .placeholder("Family...")
.cleanable()
}); });
let message_input = cx.new_view(|cx| {
TextInput::new(cx)
.appearance(false)
.text_size(Size::XSmall)
.placeholder("Hello...")
});
cx.subscribe(&user_input, move |this, _, input_event, cx| {
if let InputEvent::PressEnter = input_event {
this.add(cx);
}
})
.detach();
cx.spawn(|this, mut async_cx| { cx.spawn(|this, mut async_cx| {
let client = get_client(); let client = get_client();
@@ -75,9 +100,12 @@ impl Compose {
.detach(); .detach();
Self { Self {
input, title_input,
message_input,
user_input,
contacts, contacts,
selected, selected,
is_loading: false,
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
} }
} }
@@ -86,6 +114,61 @@ impl Compose {
self.selected.read(cx).iter().collect() self.selected.read(cx).iter().collect()
} }
fn add(&mut self, cx: &mut ViewContext<Self>) {
let content = self.user_input.read(cx).text().to_string();
let input = self.user_input.downgrade();
// Show loading spinner
self.is_loading = true;
cx.notify();
if let Ok(public_key) = PublicKey::parse(&content) {
cx.spawn(|this, mut async_cx| async move {
let query: anyhow::Result<Metadata, anyhow::Error> = async_cx
.background_executor()
.spawn(async move {
let client = get_client();
let metadata = client
.fetch_metadata(public_key, Duration::from_secs(3))
.await?;
Ok(metadata)
})
.await;
if let Ok(metadata) = query {
if let Some(view) = this.upgrade() {
_ = async_cx.update_view(&view, |this, cx| {
this.contacts.update(cx, |this, cx| {
if let Some(members) = this {
members.insert(0, Member::new(public_key, metadata));
}
cx.notify();
});
this.selected.update(cx, |this, cx| {
this.insert(public_key);
cx.notify();
});
this.is_loading = false;
cx.notify();
});
}
if let Some(input) = input.upgrade() {
_ = async_cx.update_view(&input, |input, cx| {
input.set_text("", cx);
});
}
}
})
.detach();
} else {
// Handle error
}
}
fn on_action_select(&mut self, action: &SelectContact, cx: &mut ViewContext<Self>) { fn on_action_select(&mut self, action: &SelectContact, cx: &mut ViewContext<Self>) {
self.selected.update(cx, |this, cx| { self.selected.update(cx, |this, cx| {
if this.contains(&action.0) { if this.contains(&action.0) {
@@ -95,8 +178,6 @@ impl Compose {
}; };
cx.notify(); cx.notify();
}); });
// TODO
} }
} }
@@ -110,33 +191,66 @@ impl Render for Compose {
.on_action(cx.listener(Self::on_action_select)) .on_action(cx.listener(Self::on_action_select))
.flex() .flex()
.flex_col() .flex_col()
.gap_3() .gap_1()
.child(
div()
.flex()
.flex_col()
.gap_2()
.child( .child(
div() div()
.px_2()
.text_xs() .text_xs()
.text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)) .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
.child(msg), .child(msg),
) )
.child( .child(
div() div()
.bg(cx.theme().base.step(cx, ColorScaleStep::FOUR)) .flex()
.rounded(px(cx.theme().radius)) .flex_col()
.child(
div()
.h_10()
.px_2() .px_2()
.child(self.input.clone()), .border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.flex()
.items_center()
.gap_1()
.child(div().text_xs().font_semibold().child("Title:"))
.child(self.title_input.clone()),
)
.child(
div()
.h_10()
.px_2()
.border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.flex()
.items_center()
.gap_1()
.child(div().text_xs().font_semibold().child("Message:"))
.child(self.message_input.clone()),
), ),
) )
.child( .child(
div() div()
.flex() .flex()
.flex_col() .flex_col()
.gap_1() .gap_2()
.child(div().text_xs().font_semibold().child("Contacts")) .child(div().px_2().text_xs().font_semibold().child("To:"))
.child(div().map(|this| { .child(
div()
.flex()
.items_center()
.gap_2()
.px_2()
.child(
Button::new("add")
.icon(IconName::Plus)
.small()
.rounded(ButtonRounded::Size(px(9999.)))
.loading(self.is_loading)
.on_click(cx.listener(|this, _, cx| this.add(cx))),
)
.child(self.user_input.clone()),
)
.map(|this| {
if let Some(contacts) = self.contacts.read(cx).clone() { if let Some(contacts) = self.contacts.read(cx).clone() {
this.child( this.child(
uniform_list( uniform_list(
@@ -155,9 +269,8 @@ impl Render for Compose {
div() div()
.id(ix) .id(ix)
.w_full() .w_full()
.h_10() .h_9()
.px_1p5() .px_2()
.rounded(px(cx.theme().radius))
.flex() .flex()
.items_center() .items_center()
.justify_between() .justify_between()
@@ -166,10 +279,10 @@ impl Render for Compose {
.flex() .flex()
.items_center() .items_center()
.gap_2() .gap_2()
.text_sm() .text_xs()
.child( .child(
div().flex_shrink_0().child( div().flex_shrink_0().child(
img(item.avatar()).size_8(), img(item.avatar()).size_6(),
), ),
) )
.child(item.name()), .child(item.name()),
@@ -177,7 +290,7 @@ impl Render for Compose {
.when(is_select, |this| { .when(is_select, |this| {
this.child( this.child(
Icon::new(IconName::CircleCheck) Icon::new(IconName::CircleCheck)
.size_4() .size_3()
.text_color(cx.theme().base.step( .text_color(cx.theme().base.step(
cx, cx,
ColorScaleStep::TWELVE, ColorScaleStep::TWELVE,
@@ -188,13 +301,7 @@ impl Render for Compose {
this.bg(cx this.bg(cx
.theme() .theme()
.base .base
.step(cx, ColorScaleStep::FOUR)) .step(cx, ColorScaleStep::THREE))
.text_color(
cx.theme().base.step(
cx,
ColorScaleStep::ELEVEN,
),
)
}) })
.on_click(move |_, cx| { .on_click(move |_, cx| {
cx.dispatch_action(Box::new( cx.dispatch_action(Box::new(
@@ -207,7 +314,7 @@ impl Render for Compose {
items items
}, },
) )
.h(px(320.)), .h(px(300.)),
) )
} else { } else {
this.flex() this.flex()
@@ -216,7 +323,7 @@ impl Render for Compose {
.h_16() .h_16()
.child(Indicator::new().small()) .child(Indicator::new().small())
} }
})), }),
) )
} }
} }

View File

@@ -1,7 +1,7 @@
use crate::views::sidebar::inbox::Inbox; use crate::views::sidebar::inbox::Inbox;
use compose::Compose; use compose::Compose;
use gpui::{ use gpui::{
AnyElement, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, div, px, AnyElement, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext, IntoElement, ParentElement, Render, SharedString, Styled, View, ViewContext, VisualContext,
WindowContext, WindowContext,
}; };
@@ -13,6 +13,7 @@ use ui::{
}, },
popup_menu::PopupMenu, popup_menu::PopupMenu,
scroll::ScrollbarAxis, scroll::ScrollbarAxis,
theme::{scale::ColorScaleStep, ActiveTheme},
v_flex, ContextModal, Icon, IconName, Sizable, StyledExt, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt,
}; };
@@ -61,14 +62,21 @@ impl Sidebar {
modal modal
.title("Direct Messages") .title("Direct Messages")
.width(px(420.))
.child(compose.clone()) .child(compose.clone())
.footer( .footer(
div()
.p_2()
.border_t_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.child(
Button::new("create") Button::new("create")
.label(label) .label(label)
.primary() .primary()
.bold() .bold()
.rounded(ButtonRounded::Large) .rounded(ButtonRounded::Large)
.w_full(), .w_full(),
),
) )
}) })
} }
@@ -125,7 +133,7 @@ impl Render for Sidebar {
.ghost() .ghost()
.not_centered() .not_centered()
.icon(Icon::new(IconName::ComposeFill)) .icon(Icon::new(IconName::ComposeFill))
.label("New Message") .label("Compose")
.on_click(cx.listener(|this, _, cx| this.show_compose(cx))), .on_click(cx.listener(|this, _, cx| this.show_compose(cx))),
), ),
) )

View File

@@ -345,15 +345,16 @@ impl RenderOnce for Button {
Size::Size(px) => this.size(px), Size::Size(px) => this.size(px),
Size::XSmall => this.size_5(), Size::XSmall => this.size_5(),
Size::Small => this.size_6(), Size::Small => this.size_6(),
Size::Large | Size::Medium => this.size_8(), Size::Medium => this.size_8(),
Size::Large => this.size_9(),
} }
} else { } else {
// Normal Button // Normal Button
match self.size { match self.size {
Size::Size(size) => this.px(size * 0.2), Size::Size(size) => this.px(size * 0.2),
Size::XSmall => this.h_6().px_0p5(), Size::XSmall => this.h_6().px_0p5(),
Size::Small => this.h_8().px_2(), Size::Small => this.h_7().px_2(),
_ => this.h_9().px_3(), _ => this.h_8().px_3(),
} }
} }
}) })
@@ -487,7 +488,14 @@ impl ButtonVariant {
fn text_color(&self, cx: &WindowContext) -> Hsla { fn text_color(&self, cx: &WindowContext) -> Hsla {
match self { match self {
ButtonVariant::Primary => cx.theme().base.step(cx, ColorScaleStep::TWELVE), ButtonVariant::Primary => match cx.theme().accent.name().to_string().as_str() {
"Sky" => cx.theme().base.darken(cx),
"Mint" => cx.theme().base.darken(cx),
"Lime" => cx.theme().base.darken(cx),
"Amber" => cx.theme().base.darken(cx),
"Yellow" => cx.theme().base.darken(cx),
_ => cx.theme().accent.step(cx, ColorScaleStep::ONE),
},
ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE), ButtonVariant::Link => cx.theme().accent.step(cx, ColorScaleStep::NINE),
ButtonVariant::Custom(colors) => colors.foreground, ButtonVariant::Custom(colors) => colors.foreground,
_ => cx.theme().base.step(cx, ColorScaleStep::TWELVE), _ => cx.theme().base.step(cx, ColorScaleStep::TWELVE),
@@ -535,7 +543,7 @@ impl ButtonVariant {
fn hovered(&self, cx: &WindowContext) -> ButtonVariantStyle { fn hovered(&self, cx: &WindowContext) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Custom(colors) => colors.hover, ButtonVariant::Custom(colors) => colors.hover,
@@ -560,7 +568,7 @@ impl ButtonVariant {
fn active(&self, cx: &WindowContext) -> ButtonVariantStyle { fn active(&self, cx: &WindowContext) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Custom(colors) => colors.active, ButtonVariant::Custom(colors) => colors.active,
@@ -588,7 +596,7 @@ impl ButtonVariant {
fn selected(&self, cx: &WindowContext) -> ButtonVariantStyle { fn selected(&self, cx: &WindowContext) -> ButtonVariantStyle {
let bg = match self { let bg = match self {
ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN), ButtonVariant::Primary => cx.theme().accent.step(cx, ColorScaleStep::TEN),
ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::FOUR), ButtonVariant::Ghost => cx.theme().base.step(cx, ColorScaleStep::THREE),
ButtonVariant::Link => cx.theme().transparent, ButtonVariant::Link => cx.theme().transparent,
ButtonVariant::Text => cx.theme().transparent, ButtonVariant::Text => cx.theme().transparent,
ButtonVariant::Custom(colors) => colors.active, ButtonVariant::Custom(colors) => colors.active,
@@ -618,7 +626,7 @@ impl ButtonVariant {
ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => { ButtonVariant::Link | ButtonVariant::Ghost | ButtonVariant::Text => {
cx.theme().transparent cx.theme().transparent
} }
_ => cx.theme().base.step(cx, ColorScaleStep::FOUR), _ => cx.theme().base.step(cx, ColorScaleStep::THREE),
}; };
let fg = cx.theme().base.step(cx, ColorScaleStep::ELEVEN); let fg = cx.theme().base.step(cx, ColorScaleStep::ELEVEN);

View File

@@ -60,7 +60,7 @@ impl Render for DragPanel {
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.text_xs() .text_xs()
.border_1() .border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.bg(cx.theme().base.step(cx, ColorScaleStep::TWO)) .bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
.child(self.panel.title(cx)) .child(self.panel.title(cx))
} }
@@ -577,7 +577,7 @@ impl TabPanel {
.border_r_1() .border_r_1()
.border_b_1() .border_b_1()
.h_full() .h_full()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.bg(cx.theme().base.step(cx, ColorScaleStep::TWO)) .bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
.px_2() .px_2()
.children(left_dock_button) .children(left_dock_button)
@@ -616,7 +616,7 @@ impl TabPanel {
this.rounded_l_none() this.rounded_l_none()
.border_l_2() .border_l_2()
.border_r_0() .border_r_0()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
}) })
.on_drop(cx.listener( .on_drop(cx.listener(
move |this, drag: &DragPanel, cx| { move |this, drag: &DragPanel, cx| {
@@ -661,7 +661,7 @@ impl TabPanel {
.border_l_1() .border_l_1()
.border_b_1() .border_b_1()
.h_full() .h_full()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.bg(cx.theme().base.step(cx, ColorScaleStep::TWO)) .bg(cx.theme().base.step(cx, ColorScaleStep::TWO))
.px_2() .px_2()
.gap_1() .gap_1()

View File

@@ -602,7 +602,7 @@ where
.justify_between() .justify_between()
.bg(cx.theme().background) .bg(cx.theme().background)
.border_1() .border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FOUR)) .border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.when(cx.theme().shadow, |this| this.shadow_sm()) .when(cx.theme().shadow, |this| this.shadow_sm())
.map(|this| { .map(|this| {
@@ -695,7 +695,7 @@ where
.bg(cx.theme().background) .bg(cx.theme().background)
.border_1() .border_1()
.border_color( .border_color(
cx.theme().base.step(cx, ColorScaleStep::FOUR), cx.theme().base.step(cx, ColorScaleStep::SEVEN),
) )
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.shadow_md() .shadow_md()

View File

@@ -1390,9 +1390,9 @@ impl Render for TextInput {
.when(self.multi_line, |this| this.h_auto()) .when(self.multi_line, |this| this.h_auto())
.when(self.appearance, |this| { .when(self.appearance, |this| {
this.bg(if self.disabled { this.bg(if self.disabled {
cx.theme().base.step(cx, ColorScaleStep::FOUR) cx.theme().transparent
} else { } else {
cx.theme().background cx.theme().base.step(cx, ColorScaleStep::THREE)
}) })
.rounded(px(cx.theme().radius)) .rounded(px(cx.theme().radius))
.when(cx.theme().shadow, |this| this.shadow_sm()) .when(cx.theme().shadow, |this| this.shadow_sm())

View File

@@ -5,8 +5,8 @@ use crate::{
v_flex, ContextModal, IconName, Sizable as _, StyledExt, v_flex, ContextModal, IconName, Sizable as _, StyledExt,
}; };
use gpui::{ use gpui::{
actions, anchored, div, hsla, point, prelude::FluentBuilder, px, relative, Animation, actions, anchored, div, point, prelude::FluentBuilder, px, relative, Animation,
AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle, Hsla, AnimationExt as _, AnyElement, AppContext, Bounds, ClickEvent, Div, FocusHandle,
InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point, InteractiveElement, IntoElement, KeyBinding, MouseButton, ParentElement, Pixels, Point,
RenderOnce, SharedString, Styled, WindowContext, RenderOnce, SharedString, Styled, WindowContext,
}; };
@@ -33,24 +33,11 @@ pub struct Modal {
margin_top: Option<Pixels>, margin_top: Option<Pixels>,
on_close: OnClose, on_close: OnClose,
show_close: bool, show_close: bool,
overlay: bool,
keyboard: bool, keyboard: bool,
/// This will be change when open the modal, the focus handle is create when open the modal. /// This will be change when open the modal, the focus handle is create when open the modal.
pub(crate) focus_handle: FocusHandle, pub(crate) focus_handle: FocusHandle,
pub(crate) layer_ix: usize, pub(crate) layer_ix: usize,
pub(crate) overlay_visible: bool, pub(crate) overlay: bool,
}
pub(crate) fn overlay_color(overlay: bool, cx: &WindowContext) -> Hsla {
if !overlay {
return hsla(0., 0., 0., 0.);
}
if cx.theme().appearance.is_dark() {
hsla(0., 1., 1., 0.06)
} else {
hsla(0., 0., 0., 0.06)
}
} }
impl Modal { impl Modal {
@@ -58,12 +45,10 @@ impl Modal {
let base = v_flex() let base = v_flex()
.bg(cx.theme().background) .bg(cx.theme().background)
.border_1() .border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.rounded_lg() .rounded_lg()
.shadow_xl() .shadow_xl()
.min_h_48() .min_h_48();
.p_4()
.gap_4();
Self { Self {
base, base,
@@ -77,7 +62,6 @@ impl Modal {
overlay: true, overlay: true,
keyboard: true, keyboard: true,
layer_ix: 0, layer_ix: 0,
overlay_visible: true,
on_close: Rc::new(|_, _| {}), on_close: Rc::new(|_, _| {}),
show_close: true, show_close: true,
} }
@@ -171,8 +155,8 @@ impl RenderOnce for Modal {
origin: Point::default(), origin: Point::default(),
size: view_size, size: view_size,
}; };
let offset_top = px(layer_ix as f32 * 16.); let offset_top = px(layer_ix as f32 * 2.);
let y = self.margin_top.unwrap_or(view_size.height / 10.) + offset_top; let y = self.margin_top.unwrap_or(view_size.height / 16.) + offset_top;
let x = bounds.center().x - self.width / 2.; let x = bounds.center().x - self.width / 2.;
anchored() anchored()
@@ -183,8 +167,8 @@ impl RenderOnce for Modal {
.occlude() .occlude()
.w(view_size.width) .w(view_size.width)
.h(view_size.height) .h(view_size.height)
.when(self.overlay_visible, |this| { .when(self.overlay, |this| {
this.bg(overlay_color(self.overlay, cx)) this.bg(cx.theme().base.step_alpha(cx, ColorScaleStep::EIGHT))
}) })
.on_mouse_down(MouseButton::Left, { .on_mouse_down(MouseButton::Left, {
let on_close = self.on_close.clone(); let on_close = self.on_close.clone();
@@ -221,6 +205,10 @@ impl RenderOnce for Modal {
.when_some(self.title, |this, title| { .when_some(self.title, |this, title| {
this.child( this.child(
div() div()
.flex()
.items_center()
.h_10()
.px_2()
.text_sm() .text_sm()
.font_semibold() .font_semibold()
.line_height(relative(1.)) .line_height(relative(1.))

View File

@@ -228,7 +228,7 @@ impl Render for Notification {
.relative() .relative()
.w_96() .w_96()
.border_1() .border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.bg(cx.theme().background) .bg(cx.theme().background)
.rounded_md() .rounded_md()
.shadow_md() .shadow_md()

View File

@@ -65,7 +65,7 @@ impl RenderOnce for ResizeHandle {
}) })
.child( .child(
div() div()
.bg(cx.theme().base.step(cx, ColorScaleStep::THREE)) .bg(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.when(self.axis.is_horizontal(), |this| { .when(self.axis.is_horizontal(), |this| {
this.h_full().w(HANDLE_SIZE) this.h_full().w(HANDLE_SIZE)
}) })

View File

@@ -239,7 +239,7 @@ impl Root {
// Keep only have one overlay, we only render the first modal with overlay. // Keep only have one overlay, we only render the first modal with overlay.
if has_overlay { if has_overlay {
modal.overlay_visible = false; modal.overlay = false;
} }
if modal.has_overlay() { if modal.has_overlay() {
has_overlay = true; has_overlay = true;

View File

@@ -66,7 +66,7 @@ pub trait StyledExt: Styled + Sized {
fn popover_style(self, cx: &mut WindowContext) -> Self { fn popover_style(self, cx: &mut WindowContext) -> Self {
self.bg(cx.theme().background) self.bg(cx.theme().background)
.border_1() .border_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::FOUR)) .border_color(cx.theme().base.step(cx, ColorScaleStep::SIX))
.shadow_lg() .shadow_lg()
.rounded_lg() .rounded_lg()
} }

View File

@@ -113,7 +113,7 @@ impl Element for Switch {
theme.accent.step(cx, ColorScaleStep::NINE), theme.accent.step(cx, ColorScaleStep::NINE),
theme.background, theme.background,
), ),
false => (theme.base.step(cx, ColorScaleStep::FOUR), theme.background), false => (theme.base.step(cx, ColorScaleStep::THREE), theme.background),
}; };
let (bg, toggle_bg) = match self.disabled { let (bg, toggle_bg) = match self.disabled {

View File

@@ -118,7 +118,7 @@ impl RenderOnce for Tab {
.border_x_1() .border_x_1()
.border_color(cx.theme().transparent) .border_color(cx.theme().transparent)
.when(self.selected, |this| { .when(self.selected, |this| {
this.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) this.border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
}) })
.when(!self.selected, |this| { .when(!self.selected, |this| {
this.child( this.child(
@@ -128,7 +128,7 @@ impl RenderOnce for Tab {
.bottom_0() .bottom_0()
.size_full() .size_full()
.border_b_1() .border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)), .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)),
) )
}) })
.when_some(self.prefix, |this, prefix| { .when_some(self.prefix, |this, prefix| {

View File

@@ -79,7 +79,7 @@ impl RenderOnce for TabBar {
.bottom_0() .bottom_0()
.size_full() .size_full()
.border_b_1() .border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)), .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE)),
) )
.when_some(self.prefix, |this, prefix| this.child(prefix)) .when_some(self.prefix, |this, prefix| this.child(prefix))
.child( .child(

View File

@@ -11,7 +11,7 @@ pub mod colors;
pub mod scale; pub mod scale;
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct ThemeColors { pub struct SystemColors {
pub background: Hsla, pub background: Hsla,
pub transparent: Hsla, pub transparent: Hsla,
pub scrollbar: Hsla, pub scrollbar: Hsla,
@@ -21,7 +21,7 @@ pub struct ThemeColors {
pub danger: Hsla, pub danger: Hsla,
} }
impl ThemeColors { impl SystemColors {
pub fn light() -> Self { pub fn light() -> Self {
Self { Self {
background: hsl(0.0, 0.0, 100.), background: hsl(0.0, 0.0, 100.),
@@ -93,7 +93,7 @@ impl Appearance {
} }
pub struct Theme { pub struct Theme {
colors: ThemeColors, colors: SystemColors,
/// Base colors. /// Base colors.
pub base: ColorScaleSet, pub base: ColorScaleSet,
/// Accent colors. /// Accent colors.
@@ -109,7 +109,7 @@ pub struct Theme {
} }
impl Deref for Theme { impl Deref for Theme {
type Target = ThemeColors; type Target = SystemColors;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.colors &self.colors
@@ -159,8 +159,8 @@ impl Theme {
fn new(appearance: Appearance) -> Self { fn new(appearance: Appearance) -> Self {
let color_scales = default_color_scales(); let color_scales = default_color_scales();
let colors = match appearance { let colors = match appearance {
Appearance::Light => ThemeColors::light(), Appearance::Light => SystemColors::light(),
Appearance::Dark => ThemeColors::dark(), Appearance::Dark => SystemColors::dark(),
}; };
Theme { Theme {
@@ -174,7 +174,7 @@ impl Theme {
} else { } else {
"FreeMono".into() "FreeMono".into()
}, },
radius: 6.0, radius: 5.0,
shadow: false, shadow: false,
scrollbar_show: ScrollbarShow::default(), scrollbar_show: ScrollbarShow::default(),
appearance, appearance,

View File

@@ -292,4 +292,11 @@ impl ColorScaleSet {
Appearance::Dark => self.dark_alpha.step(step), Appearance::Dark => self.dark_alpha.step(step),
} }
} }
pub fn darken(&self, cx: &AppContext) -> Hsla {
match cx.theme().appearance {
Appearance::Light => self.light.step_12(),
Appearance::Dark => self.dark.step_1(),
}
}
} }

View File

@@ -252,7 +252,7 @@ impl RenderOnce for TitleBar {
.justify_between() .justify_between()
.h(HEIGHT) .h(HEIGHT)
.border_b_1() .border_b_1()
.border_color(cx.theme().base.step(cx, ColorScaleStep::THREE)) .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
.bg(cx.theme().base.step(cx, ColorScaleStep::ONE)) .bg(cx.theme().base.step(cx, ColorScaleStep::ONE))
.when(cx.is_fullscreen(), |this| this.pl(px(12.))) .when(cx.is_fullscreen(), |this| this.pl(px(12.)))
.on_double_click(|_, cx| cx.zoom_window()) .on_double_click(|_, cx| cx.zoom_window())