Detailed changes
@@ -130,6 +130,7 @@ impl ChatPanel {
fs,
client,
channel_store,
+
active_chat: Default::default(),
pending_serialization: Task::ready(None),
message_list,
@@ -328,12 +329,26 @@ impl ChatPanel {
}
fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- let message = self.active_chat.as_ref().unwrap().0.read(cx).message(ix);
+ let (message, is_continuation, is_last) = {
+ let active_chat = self.active_chat.as_ref().unwrap().0.read(cx);
+ let last_message = active_chat.message(ix.saturating_sub(1));
+ let this_message = active_chat.message(ix);
+ let is_continuation = last_message.id != this_message.id
+ && this_message.sender.id == last_message.sender.id;
+
+ (
+ active_chat.message(ix),
+ is_continuation,
+ active_chat.message_count() == ix + 1,
+ )
+ };
let now = OffsetDateTime::now_utc();
let theme = theme::current(cx);
let style = if message.is_pending() {
&theme.chat_panel.pending_message
+ } else if is_continuation {
+ &theme.chat_panel.continuation_message
} else {
&theme.chat_panel.message
};
@@ -349,49 +364,103 @@ impl ChatPanel {
enum DeleteMessage {}
let body = message.body.clone();
- Flex::column()
- .with_child(
- Flex::row()
- .with_child(
- Label::new(
- message.sender.github_login.clone(),
- style.sender.text.clone(),
+ if is_continuation {
+ Flex::row()
+ .with_child(Text::new(body, style.body.clone()))
+ .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()
+ }))
+ .contained()
+ .with_style(style.container)
+ .with_margin_bottom(if is_last {
+ theme.chat_panel.last_message_bottom_spacing
+ } else {
+ 0.
+ })
+ .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()
+ })
+ .contained()
+ .with_margin_right(4.),
)
- .contained()
- .with_style(style.sender.container),
- )
- .with_child(
- Label::new(
- format_timestamp(message.timestamp, now, self.local_timezone),
- style.timestamp.text.clone(),
+ .with_child(
+ Label::new(
+ message.sender.github_login.clone(),
+ style.sender.text.clone(),
+ )
+ .contained()
+ .with_style(style.sender.container),
)
- .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_child(
+ Label::new(
+ format_timestamp(message.timestamp, now, self.local_timezone),
+ style.timestamp.text.clone(),
+ )
+ .contained()
+ .with_style(style.timestamp.container),
)
- .with_padding(Padding::uniform(2.))
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, this, cx| {
- this.remove_message(id, cx);
- })
- .flex_float()
- })),
- )
- .with_child(Text::new(body, style.body.clone()))
- .contained()
- .with_style(style.container)
- .into_any()
+ .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(Text::new(body, style.body.clone()))
+ .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> {
@@ -1937,6 +1937,8 @@ impl CollabPanel {
is_dragged_over = true;
}
+ let has_messages_notification = channel.unseen_message_id.is_some();
+
MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
let row_hovered = state.hovered();
@@ -2022,24 +2024,33 @@ impl CollabPanel {
.flex(1., true)
})
.with_child(
- MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |_, _| {
+ MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
+ let container_style = collab_theme
+ .disclosure
+ .button
+ .style_for(mouse_state)
+ .container;
+
if channel.unseen_message_id.is_some() {
Svg::new("icons/conversations.svg")
.with_color(collab_theme.channel_note_active_color)
.constrained()
.with_width(collab_theme.channel_hash.width)
+ .contained()
+ .with_style(container_style)
+ .with_uniform_padding(4.)
.into_any()
} else if row_hovered {
Svg::new("icons/conversations.svg")
.with_color(collab_theme.channel_hash.color)
.constrained()
.with_width(collab_theme.channel_hash.width)
+ .contained()
+ .with_style(container_style)
+ .with_uniform_padding(4.)
.into_any()
} else {
- Empty::new()
- .constrained()
- .with_width(collab_theme.channel_hash.width)
- .into_any()
+ Empty::new().into_any()
}
})
.on_click(MouseButton::Left, move |_, this, cx| {
@@ -2056,7 +2067,12 @@ impl CollabPanel {
.with_margin_right(4.),
)
.with_child(
- MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |_, cx| {
+ MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
+ let container_style = collab_theme
+ .disclosure
+ .button
+ .style_for(mouse_state)
+ .container;
if row_hovered || channel.unseen_note_version.is_some() {
Svg::new("icons/file.svg")
.with_color(if channel.unseen_note_version.is_some() {
@@ -2067,6 +2083,8 @@ impl CollabPanel {
.constrained()
.with_width(collab_theme.channel_hash.width)
.contained()
+ .with_style(container_style)
+ .with_uniform_padding(4.)
.with_margin_right(collab_theme.channel_hash.container.margin.left)
.with_tooltip::<NotesTooltip>(
ix as usize,
@@ -2076,13 +2094,16 @@ impl CollabPanel {
cx,
)
.into_any()
- } else {
+ } else if has_messages_notification {
Empty::new()
.constrained()
.with_width(collab_theme.channel_hash.width)
.contained()
+ .with_uniform_padding(4.)
.with_margin_right(collab_theme.channel_hash.container.margin.left)
.into_any()
+ } else {
+ Empty::new().into_any()
}
})
.on_click(MouseButton::Left, move |_, this, cx| {
@@ -635,6 +635,8 @@ pub struct ChatPanel {
pub channel_select: ChannelSelect,
pub input_editor: FieldEditor,
pub message: ChatMessage,
+ pub continuation_message: ChatMessage,
+ pub last_message_bottom_spacing: f32,
pub pending_message: ChatMessage,
pub sign_in_prompt: Interactive<TextStyle>,
pub icon_button: Interactive<IconButton>,
@@ -87,7 +87,19 @@ export default function chat_panel(): any {
...text(layer, "sans", "base", { weight: "bold" }),
},
timestamp: text(layer, "sans", "base", "disabled"),
- margin: { bottom: SPACING }
+ margin: { top: SPACING }
+ },
+ last_message_bottom_spacing: SPACING,
+ continuation_message: {
+ body: text(layer, "sans", "base"),
+ sender: {
+ margin: {
+ right: 8,
+ },
+ ...text(layer, "sans", "base", { weight: "bold" }),
+ },
+ timestamp: text(layer, "sans", "base", "disabled"),
+
},
pending_message: {
body: text(layer, "sans", "base"),
@@ -21,6 +21,7 @@ export default function contacts_panel(): any {
...text(theme.lowest, "sans", "base"),
button: icon_button({ variant: "ghost" }),
spacing: 4,
+ padding: 4,
},
}
}