mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 18:33:09 +00:00
Improve chat rendering
This commit is contained in:
parent
44ada52185
commit
f57d563578
3 changed files with 213 additions and 109 deletions
|
@ -13,8 +13,8 @@ use gpui::{
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
serde_json,
|
serde_json,
|
||||||
views::{ItemType, Select, SelectStyle},
|
views::{ItemType, Select, SelectStyle},
|
||||||
AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View,
|
AnyViewHandle, AppContext, AsyncAppContext, Entity, ImageData, ModelHandle, Subscription, Task,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
View, ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use language::{language_settings::SoftWrap, LanguageRegistry};
|
use language::{language_settings::SoftWrap, LanguageRegistry};
|
||||||
use markdown_element::{MarkdownData, MarkdownElement};
|
use markdown_element::{MarkdownData, MarkdownElement};
|
||||||
|
@ -363,22 +363,23 @@ impl ChatPanel {
|
||||||
&& this_message.sender.id == last_message.sender.id;
|
&& this_message.sender.id == last_message.sender.id;
|
||||||
|
|
||||||
(
|
(
|
||||||
active_chat.message(ix),
|
active_chat.message(ix).clone(),
|
||||||
is_continuation,
|
is_continuation,
|
||||||
active_chat.message_count() == ix + 1,
|
active_chat.message_count() == ix + 1,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let is_pending = message.is_pending();
|
||||||
let markdown = self.markdown_data.entry(message.id).or_insert_with(|| {
|
let markdown = self.markdown_data.entry(message.id).or_insert_with(|| {
|
||||||
Arc::new(markdown_element::render_markdown(
|
Arc::new(markdown_element::render_markdown(
|
||||||
message.body.clone(),
|
message.body,
|
||||||
&self.languages,
|
&self.languages,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
let now = OffsetDateTime::now_utc();
|
let now = OffsetDateTime::now_utc();
|
||||||
let theme = theme::current(cx);
|
let theme = theme::current(cx);
|
||||||
let style = if message.is_pending() {
|
let style = if is_pending {
|
||||||
&theme.chat_panel.pending_message
|
&theme.chat_panel.pending_message
|
||||||
} else if is_continuation {
|
} else if is_continuation {
|
||||||
&theme.chat_panel.continuation_message
|
&theme.chat_panel.continuation_message
|
||||||
|
@ -394,60 +395,39 @@ impl ChatPanel {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DeleteMessage {}
|
enum MessageBackgroundHighlight {}
|
||||||
|
MouseEventHandler::new::<MessageBackgroundHighlight, _>(ix, cx, |state, cx| {
|
||||||
|
let container = style.container.style_for(state);
|
||||||
if is_continuation {
|
if is_continuation {
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::column()
|
MarkdownElement::new(
|
||||||
.with_child(MarkdownElement::new(
|
|
||||||
markdown.clone(),
|
markdown.clone(),
|
||||||
style.body.clone(),
|
style.body.clone(),
|
||||||
theme.editor.syntax.clone(),
|
theme.editor.syntax.clone(),
|
||||||
theme.editor.document_highlight_read_background,
|
theme.editor.document_highlight_read_background,
|
||||||
))
|
)
|
||||||
.flex(1., true),
|
.flex(1., true),
|
||||||
)
|
)
|
||||||
.with_children(message_id_to_remove.map(|id| {
|
.with_child(render_remove(message_id_to_remove, cx, &theme))
|
||||||
MouseEventHandler::new::<DeleteMessage, _>(id as usize, cx, |mouse_state, _| {
|
.contained()
|
||||||
let button_style = theme.chat_panel.icon_button.style_for(mouse_state);
|
.with_style(*container)
|
||||||
render_icon_button(button_style, "icons/x.svg")
|
.with_margin_bottom(if is_last {
|
||||||
.aligned()
|
theme.chat_panel.last_message_bottom_spacing
|
||||||
.into_any()
|
} else {
|
||||||
|
0.
|
||||||
})
|
})
|
||||||
.with_padding(Padding::uniform(2.))
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
this.remove_message(id, cx);
|
|
||||||
})
|
|
||||||
.flex_float()
|
|
||||||
}))
|
|
||||||
.into_any()
|
.into_any()
|
||||||
} else {
|
} else {
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
message
|
Flex::row()
|
||||||
.sender
|
.with_child(render_avatar(
|
||||||
.avatar
|
message.sender.avatar.clone(),
|
||||||
.clone()
|
&theme,
|
||||||
.map(|avatar| {
|
))
|
||||||
Image::from_data(avatar)
|
|
||||||
.with_style(theme.collab_panel.channel_avatar)
|
|
||||||
.into_any()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
Empty::new()
|
|
||||||
.constrained()
|
|
||||||
.with_width(
|
|
||||||
theme.collab_panel.channel_avatar.width.unwrap_or(12.),
|
|
||||||
)
|
|
||||||
.into_any()
|
|
||||||
})
|
|
||||||
.contained()
|
|
||||||
.with_margin_right(4.),
|
|
||||||
)
|
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
message.sender.github_login.clone(),
|
message.sender.github_login.clone(),
|
||||||
|
@ -458,41 +438,38 @@ impl ChatPanel {
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Label::new(
|
Label::new(
|
||||||
format_timestamp(message.timestamp, now, self.local_timezone),
|
format_timestamp(
|
||||||
|
message.timestamp,
|
||||||
|
now,
|
||||||
|
self.local_timezone,
|
||||||
|
),
|
||||||
style.timestamp.text.clone(),
|
style.timestamp.text.clone(),
|
||||||
)
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.timestamp.container),
|
.with_style(style.timestamp.container),
|
||||||
)
|
)
|
||||||
.with_children(message_id_to_remove.map(|id| {
|
.align_children_center()
|
||||||
MouseEventHandler::new::<DeleteMessage, _>(
|
.flex(1., true),
|
||||||
id as usize,
|
|
||||||
cx,
|
|
||||||
|mouse_state, _| {
|
|
||||||
let button_style =
|
|
||||||
theme.chat_panel.icon_button.style_for(mouse_state);
|
|
||||||
render_icon_button(button_style, "icons/x.svg")
|
|
||||||
.aligned()
|
|
||||||
.into_any()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.with_padding(Padding::uniform(2.))
|
.with_child(render_remove(message_id_to_remove, cx, &theme))
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_click(MouseButton::Left, move |_, this, cx| {
|
|
||||||
this.remove_message(id, cx);
|
|
||||||
})
|
|
||||||
.flex_float()
|
|
||||||
}))
|
|
||||||
.align_children_center(),
|
.align_children_center(),
|
||||||
)
|
)
|
||||||
.with_child(MarkdownElement::new(
|
.with_child(
|
||||||
|
Flex::row()
|
||||||
|
.with_child(
|
||||||
|
MarkdownElement::new(
|
||||||
markdown.clone(),
|
markdown.clone(),
|
||||||
style.body.clone(),
|
style.body.clone(),
|
||||||
theme.editor.syntax.clone(),
|
theme.editor.syntax.clone(),
|
||||||
theme.editor.document_highlight_read_background,
|
theme.editor.document_highlight_read_background,
|
||||||
))
|
)
|
||||||
|
.flex(1., true),
|
||||||
|
)
|
||||||
|
// Add a spacer to make everything line up
|
||||||
|
.with_child(render_remove(None, cx, &theme)),
|
||||||
|
)
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(style.container)
|
.with_style(*container)
|
||||||
.with_margin_bottom(if is_last {
|
.with_margin_bottom(if is_last {
|
||||||
theme.chat_panel.last_message_bottom_spacing
|
theme.chat_panel.last_message_bottom_spacing
|
||||||
} else {
|
} else {
|
||||||
|
@ -500,6 +477,8 @@ impl ChatPanel {
|
||||||
})
|
})
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_input_box(&self, theme: &Arc<Theme>, cx: &AppContext) -> AnyElement<Self> {
|
fn render_input_box(&self, theme: &Arc<Theme>, cx: &AppContext) -> AnyElement<Self> {
|
||||||
|
@ -698,6 +677,72 @@ impl ChatPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_avatar(avatar: Option<Arc<ImageData>>, theme: &Arc<Theme>) -> AnyElement<ChatPanel> {
|
||||||
|
let avatar_style = theme.chat_panel.avatar;
|
||||||
|
|
||||||
|
avatar
|
||||||
|
.map(|avatar| {
|
||||||
|
Image::from_data(avatar)
|
||||||
|
.with_style(avatar_style.image)
|
||||||
|
.aligned()
|
||||||
|
.contained()
|
||||||
|
.with_corner_radius(avatar_style.outer_corner_radius)
|
||||||
|
.constrained()
|
||||||
|
.with_width(avatar_style.outer_width)
|
||||||
|
.with_height(avatar_style.outer_width)
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
Empty::new()
|
||||||
|
.constrained()
|
||||||
|
.with_width(avatar_style.outer_width)
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
.contained()
|
||||||
|
.with_style(theme.chat_panel.avatar_container)
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_remove(
|
||||||
|
message_id_to_remove: Option<u64>,
|
||||||
|
cx: &mut ViewContext<'_, '_, ChatPanel>,
|
||||||
|
theme: &Arc<Theme>,
|
||||||
|
) -> AnyElement<ChatPanel> {
|
||||||
|
enum DeleteMessage {}
|
||||||
|
|
||||||
|
message_id_to_remove
|
||||||
|
.map(|id| {
|
||||||
|
MouseEventHandler::new::<DeleteMessage, _>(id as usize, cx, |mouse_state, _| {
|
||||||
|
let button_style = theme.chat_panel.icon_button.style_for(mouse_state);
|
||||||
|
render_icon_button(button_style, "icons/x.svg")
|
||||||
|
.aligned()
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
.with_padding(Padding::uniform(2.))
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(MouseButton::Left, move |_, this, cx| {
|
||||||
|
this.remove_message(id, cx);
|
||||||
|
})
|
||||||
|
.flex_float()
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let style = theme.chat_panel.icon_button.default;
|
||||||
|
|
||||||
|
Empty::new()
|
||||||
|
.constrained()
|
||||||
|
.with_width(style.icon_width)
|
||||||
|
.aligned()
|
||||||
|
.constrained()
|
||||||
|
.with_width(style.button_width)
|
||||||
|
.with_height(style.button_width)
|
||||||
|
.contained()
|
||||||
|
.with_uniform_padding(2.)
|
||||||
|
.flex_float()
|
||||||
|
.into_any()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity for ChatPanel {
|
impl Entity for ChatPanel {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
}
|
}
|
||||||
|
|
|
@ -634,6 +634,8 @@ pub struct ChatPanel {
|
||||||
pub list: ContainerStyle,
|
pub list: ContainerStyle,
|
||||||
pub channel_select: ChannelSelect,
|
pub channel_select: ChannelSelect,
|
||||||
pub input_editor: FieldEditor,
|
pub input_editor: FieldEditor,
|
||||||
|
pub avatar: AvatarStyle,
|
||||||
|
pub avatar_container: ContainerStyle,
|
||||||
pub message: ChatMessage,
|
pub message: ChatMessage,
|
||||||
pub continuation_message: ChatMessage,
|
pub continuation_message: ChatMessage,
|
||||||
pub last_message_bottom_spacing: f32,
|
pub last_message_bottom_spacing: f32,
|
||||||
|
@ -645,7 +647,7 @@ pub struct ChatPanel {
|
||||||
#[derive(Deserialize, Default, JsonSchema)]
|
#[derive(Deserialize, Default, JsonSchema)]
|
||||||
pub struct ChatMessage {
|
pub struct ChatMessage {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub container: ContainerStyle,
|
pub container: Interactive<ContainerStyle>,
|
||||||
pub body: TextStyle,
|
pub body: TextStyle,
|
||||||
pub sender: ContainedText,
|
pub sender: ContainedText,
|
||||||
pub timestamp: ContainedText,
|
pub timestamp: ContainedText,
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
} from "./components"
|
} from "./components"
|
||||||
import { icon_button } from "../component/icon_button"
|
import { icon_button } from "../component/icon_button"
|
||||||
import { useTheme } from "../theme"
|
import { useTheme } from "../theme"
|
||||||
|
import { interactive } from "../element"
|
||||||
|
|
||||||
export default function chat_panel(): any {
|
export default function chat_panel(): any {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
@ -27,11 +28,23 @@ export default function chat_panel(): any {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
background: background(layer),
|
background: background(layer),
|
||||||
list: {
|
avatar: {
|
||||||
margin: {
|
icon_width: 24,
|
||||||
left: SPACING,
|
icon_height: 24,
|
||||||
right: SPACING,
|
corner_radius: 4,
|
||||||
|
outer_width: 24,
|
||||||
|
outer_corner_radius: 16,
|
||||||
|
},
|
||||||
|
avatar_container: {
|
||||||
|
padding: {
|
||||||
|
right: 6,
|
||||||
|
left: 2,
|
||||||
|
top: 2,
|
||||||
|
bottom: 2,
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
|
||||||
},
|
},
|
||||||
channel_select: {
|
channel_select: {
|
||||||
header: {
|
header: {
|
||||||
|
@ -79,6 +92,22 @@ export default function chat_panel(): any {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
|
...interactive({
|
||||||
|
base: {
|
||||||
|
margin: { top: SPACING },
|
||||||
|
padding: {
|
||||||
|
top: 4,
|
||||||
|
bottom: 4,
|
||||||
|
left: SPACING / 2,
|
||||||
|
right: SPACING / 3,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
hovered: {
|
||||||
|
background: background(layer, "hovered"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
body: text(layer, "sans", "base"),
|
body: text(layer, "sans", "base"),
|
||||||
sender: {
|
sender: {
|
||||||
margin: {
|
margin: {
|
||||||
|
@ -87,7 +116,6 @@ export default function chat_panel(): any {
|
||||||
...text(layer, "sans", "base", { weight: "bold" }),
|
...text(layer, "sans", "base", { weight: "bold" }),
|
||||||
},
|
},
|
||||||
timestamp: text(layer, "sans", "base", "disabled"),
|
timestamp: text(layer, "sans", "base", "disabled"),
|
||||||
margin: { top: SPACING }
|
|
||||||
},
|
},
|
||||||
last_message_bottom_spacing: SPACING,
|
last_message_bottom_spacing: SPACING,
|
||||||
continuation_message: {
|
continuation_message: {
|
||||||
|
@ -99,7 +127,21 @@ export default function chat_panel(): any {
|
||||||
...text(layer, "sans", "base", { weight: "bold" }),
|
...text(layer, "sans", "base", { weight: "bold" }),
|
||||||
},
|
},
|
||||||
timestamp: text(layer, "sans", "base", "disabled"),
|
timestamp: text(layer, "sans", "base", "disabled"),
|
||||||
|
...interactive({
|
||||||
|
base: {
|
||||||
|
padding: {
|
||||||
|
top: 4,
|
||||||
|
bottom: 4,
|
||||||
|
left: SPACING / 2,
|
||||||
|
right: SPACING / 3,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
hovered: {
|
||||||
|
background: background(layer, "hovered"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
pending_message: {
|
pending_message: {
|
||||||
body: text(layer, "sans", "base"),
|
body: text(layer, "sans", "base"),
|
||||||
|
@ -110,6 +152,21 @@ export default function chat_panel(): any {
|
||||||
...text(layer, "sans", "base", "disabled"),
|
...text(layer, "sans", "base", "disabled"),
|
||||||
},
|
},
|
||||||
timestamp: text(layer, "sans", "base"),
|
timestamp: text(layer, "sans", "base"),
|
||||||
|
...interactive({
|
||||||
|
base: {
|
||||||
|
padding: {
|
||||||
|
top: 4,
|
||||||
|
bottom: 4,
|
||||||
|
left: SPACING / 2,
|
||||||
|
right: SPACING / 3,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state: {
|
||||||
|
hovered: {
|
||||||
|
background: background(layer, "hovered"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
sign_in_prompt: {
|
sign_in_prompt: {
|
||||||
default: text(layer, "sans", "base"),
|
default: text(layer, "sans", "base"),
|
||||||
|
|
Loading…
Reference in a new issue