Improve chat rendering

This commit is contained in:
Mikayla 2023-10-05 11:58:41 -07:00
parent 44ada52185
commit f57d563578
No known key found for this signature in database
3 changed files with 213 additions and 109 deletions

View File

@ -13,8 +13,8 @@ use gpui::{
platform::{CursorStyle, MouseButton},
serde_json,
views::{ItemType, Select, SelectStyle},
AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle, Subscription, Task, View,
ViewContext, ViewHandle, WeakViewHandle,
AnyViewHandle, AppContext, AsyncAppContext, Entity, ImageData, ModelHandle, Subscription, Task,
View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::{language_settings::SoftWrap, LanguageRegistry};
use markdown_element::{MarkdownData, MarkdownElement};
@ -363,22 +363,23 @@ impl ChatPanel {
&& this_message.sender.id == last_message.sender.id;
(
active_chat.message(ix),
active_chat.message(ix).clone(),
is_continuation,
active_chat.message_count() == ix + 1,
)
};
let is_pending = message.is_pending();
let markdown = self.markdown_data.entry(message.id).or_insert_with(|| {
Arc::new(markdown_element::render_markdown(
message.body.clone(),
message.body,
&self.languages,
))
});
let now = OffsetDateTime::now_utc();
let theme = theme::current(cx);
let style = if message.is_pending() {
let style = if is_pending {
&theme.chat_panel.pending_message
} else if is_continuation {
&theme.chat_panel.continuation_message
@ -394,112 +395,90 @@ impl ChatPanel {
None
};
enum DeleteMessage {}
if is_continuation {
Flex::row()
.with_child(
Flex::column()
.with_child(MarkdownElement::new(
enum MessageBackgroundHighlight {}
MouseEventHandler::new::<MessageBackgroundHighlight, _>(ix, cx, |state, cx| {
let container = style.container.style_for(state);
if is_continuation {
Flex::row()
.with_child(
MarkdownElement::new(
markdown.clone(),
style.body.clone(),
theme.editor.syntax.clone(),
theme.editor.document_highlight_read_background,
))
)
.flex(1., true),
)
.with_children(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_child(render_remove(message_id_to_remove, cx, &theme))
.contained()
.with_style(*container)
.with_margin_bottom(if is_last {
theme.chat_panel.last_message_bottom_spacing
} 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()
} else {
Flex::column()
.with_child(
Flex::row()
.with_child(
message
.sender
.avatar
.clone()
.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()
} else {
Flex::column()
.with_child(
Flex::row()
.with_child(
Flex::row()
.with_child(render_avatar(
message.sender.avatar.clone(),
&theme,
))
.with_child(
Label::new(
message.sender.github_login.clone(),
style.sender.text.clone(),
)
.into_any()
})
.contained()
.with_margin_right(4.),
)
.with_child(
Label::new(
message.sender.github_login.clone(),
style.sender.text.clone(),
.contained()
.with_style(style.sender.container),
)
.with_child(
Label::new(
format_timestamp(
message.timestamp,
now,
self.local_timezone,
),
style.timestamp.text.clone(),
)
.contained()
.with_style(style.timestamp.container),
)
.align_children_center()
.flex(1., true),
)
.contained()
.with_style(style.sender.container),
)
.with_child(
Label::new(
format_timestamp(message.timestamp, now, self.local_timezone),
style.timestamp.text.clone(),
.with_child(render_remove(message_id_to_remove, cx, &theme))
.align_children_center(),
)
.with_child(
Flex::row()
.with_child(
MarkdownElement::new(
markdown.clone(),
style.body.clone(),
theme.editor.syntax.clone(),
theme.editor.document_highlight_read_background,
)
.flex(1., true),
)
.contained()
.with_style(style.timestamp.container),
)
.with_children(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()
}))
.align_children_center(),
)
.with_child(MarkdownElement::new(
markdown.clone(),
style.body.clone(),
theme.editor.syntax.clone(),
theme.editor.document_highlight_read_background,
))
.contained()
.with_style(style.container)
.with_margin_bottom(if is_last {
theme.chat_panel.last_message_bottom_spacing
} else {
0.
})
.into_any()
}
// Add a spacer to make everything line up
.with_child(render_remove(None, cx, &theme)),
)
.contained()
.with_style(*container)
.with_margin_bottom(if is_last {
theme.chat_panel.last_message_bottom_spacing
} else {
0.
})
.into_any()
}
})
.into_any()
}
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 {
type Event = Event;
}

View File

@ -634,6 +634,8 @@ pub struct ChatPanel {
pub list: ContainerStyle,
pub channel_select: ChannelSelect,
pub input_editor: FieldEditor,
pub avatar: AvatarStyle,
pub avatar_container: ContainerStyle,
pub message: ChatMessage,
pub continuation_message: ChatMessage,
pub last_message_bottom_spacing: f32,
@ -645,7 +647,7 @@ pub struct ChatPanel {
#[derive(Deserialize, Default, JsonSchema)]
pub struct ChatMessage {
#[serde(flatten)]
pub container: ContainerStyle,
pub container: Interactive<ContainerStyle>,
pub body: TextStyle,
pub sender: ContainedText,
pub timestamp: ContainedText,

View File

@ -5,6 +5,7 @@ import {
} from "./components"
import { icon_button } from "../component/icon_button"
import { useTheme } from "../theme"
import { interactive } from "../element"
export default function chat_panel(): any {
const theme = useTheme()
@ -27,11 +28,23 @@ export default function chat_panel(): any {
return {
background: background(layer),
list: {
margin: {
left: SPACING,
right: SPACING,
avatar: {
icon_width: 24,
icon_height: 24,
corner_radius: 4,
outer_width: 24,
outer_corner_radius: 16,
},
avatar_container: {
padding: {
right: 6,
left: 2,
top: 2,
bottom: 2,
}
},
list: {
},
channel_select: {
header: {
@ -79,6 +92,22 @@ export default function chat_panel(): any {
},
},
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"),
sender: {
margin: {
@ -87,7 +116,6 @@ export default function chat_panel(): any {
...text(layer, "sans", "base", { weight: "bold" }),
},
timestamp: text(layer, "sans", "base", "disabled"),
margin: { top: SPACING }
},
last_message_bottom_spacing: SPACING,
continuation_message: {
@ -99,7 +127,21 @@ export default function chat_panel(): any {
...text(layer, "sans", "base", { weight: "bold" }),
},
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: {
body: text(layer, "sans", "base"),
@ -110,6 +152,21 @@ export default function chat_panel(): any {
...text(layer, "sans", "base", "disabled"),
},
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: {
default: text(layer, "sans", "base"),