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,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;
} }

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