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}, 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,112 +395,90 @@ impl ChatPanel {
None None
}; };
enum DeleteMessage {} enum MessageBackgroundHighlight {}
MouseEventHandler::new::<MessageBackgroundHighlight, _>(ix, cx, |state, cx| {
if is_continuation { let container = style.container.style_for(state);
Flex::row() if is_continuation {
.with_child( Flex::row()
Flex::column() .with_child(
.with_child(MarkdownElement::new( 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.)) .into_any()
.with_cursor_style(CursorStyle::PointingHand) } else {
.on_click(MouseButton::Left, move |_, this, cx| { Flex::column()
this.remove_message(id, cx); .with_child(
}) Flex::row()
.flex_float() .with_child(
})) Flex::row()
.into_any() .with_child(render_avatar(
} else { message.sender.avatar.clone(),
Flex::column() &theme,
.with_child( ))
Flex::row() .with_child(
.with_child( Label::new(
message message.sender.github_login.clone(),
.sender style.sender.text.clone(),
.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() .contained()
}) .with_style(style.sender.container),
.contained() )
.with_margin_right(4.), .with_child(
) Label::new(
.with_child( format_timestamp(
Label::new( message.timestamp,
message.sender.github_login.clone(), now,
style.sender.text.clone(), self.local_timezone,
),
style.timestamp.text.clone(),
)
.contained()
.with_style(style.timestamp.container),
)
.align_children_center()
.flex(1., true),
) )
.contained() .with_child(render_remove(message_id_to_remove, cx, &theme))
.with_style(style.sender.container), .align_children_center(),
) )
.with_child( .with_child(
Label::new( Flex::row()
format_timestamp(message.timestamp, now, self.local_timezone), .with_child(
style.timestamp.text.clone(), MarkdownElement::new(
markdown.clone(),
style.body.clone(),
theme.editor.syntax.clone(),
theme.editor.document_highlight_read_background,
)
.flex(1., true),
) )
.contained() // Add a spacer to make everything line up
.with_style(style.timestamp.container), .with_child(render_remove(None, cx, &theme)),
) )
.with_children(message_id_to_remove.map(|id| { .contained()
MouseEventHandler::new::<DeleteMessage, _>( .with_style(*container)
id as usize, .with_margin_bottom(if is_last {
cx, theme.chat_panel.last_message_bottom_spacing
|mouse_state, _| { } else {
let button_style = 0.
theme.chat_panel.icon_button.style_for(mouse_state); })
render_icon_button(button_style, "icons/x.svg") .into_any()
.aligned() }
.into_any() })
}, .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()
}
} }
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;
} }

View file

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

View file

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