diff --git a/src/home/room_preview.rs b/src/home/room_preview.rs index c330fd4..d1fa5c2 100644 --- a/src/home/room_preview.rs +++ b/src/home/room_preview.rs @@ -255,6 +255,7 @@ impl RoomPreviewContent { let message_text_color; let room_name_color; let timestamp_color; + let code_bg_color; // TODO: This is quite verbose, makepad should provide a way to override this at a higher level. if is_selected { @@ -262,11 +263,13 @@ impl RoomPreviewContent { message_text_color = vec3(1., 1., 1.); // COLOR_PRIMARY room_name_color = vec3(1., 1., 1.); // COLOR_PRIMARY timestamp_color = vec3(1., 1., 1.); // COLOR_PRIMARY + code_bg_color = vec3(0.3, 0.3, 0.3); // a darker gray, used for `code_color` and `quote_bg_color` } else { bg_color = vec3(1., 1., 1.); // COLOR_PRIMARY message_text_color = vec3(0.267, 0.267, 0.267); // MESSAGE_TEXT_COLOR room_name_color = vec3(0., 0., 0.); timestamp_color = vec3(0.6, 0.6, 0.6); + code_bg_color = vec3(0.929, 0.929, 0.929); // #EDEDED, see `code_color` and `quote_bg_color` } self.view.apply_over( @@ -312,6 +315,10 @@ impl RoomPreviewContent { draw_italic: { color: (message_text_color) }, draw_bold: { color: (message_text_color) }, draw_bold_italic: { color: (message_text_color) }, + draw_block: { + quote_bg_color: (code_bg_color), + code_color: (code_bg_color), + } } } plaintext_view = { diff --git a/src/home/room_screen.rs b/src/home/room_screen.rs index 24c9b67..8f8a564 100644 --- a/src/home/room_screen.rs +++ b/src/home/room_screen.rs @@ -9,7 +9,7 @@ use matrix_sdk::{ ruma::{ events::room::{ message::{ - FormattedBody, ImageMessageEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, TextMessageEventContent + EmoteMessageEventContent, FormattedBody, ImageMessageEventContent, LocationMessageEventContent, MessageFormat, MessageType, NoticeMessageEventContent, RoomMessageEventContent, TextMessageEventContent }, MediaSource, }, matrix_uri::MatrixId, uint, EventId, MatrixToUri, MatrixUri, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, UserId @@ -37,6 +37,8 @@ use super::loading_modal::{LoadingModalAction, LoadingModalState}; const GEO_URI_SCHEME: &str = "geo:"; +const MESSAGE_NOTICE_TEXT_COLOR: Vec3 = Vec3 { x: 0.5, y: 0.5, z: 0.5 }; + live_design! { import makepad_draw::shader::std::*; import makepad_widgets::base::*; @@ -227,7 +229,7 @@ live_design! { height: Fit, padding: {top: 5.0} - html_content = { + html_content = { width: Fill, height: Fit, padding: { bottom: 5.0, top: 0.0 }, @@ -2375,6 +2377,8 @@ fn populate_message_view( let ts_millis = event_tl_item.timestamp(); + let mut is_notice = false; // whether this message is a Notice + // Determine whether we can use a more compact UI view that hides the user's profile info // if the previous message was sent by the same user within 10 minutes. let use_compact_view = match prev_event.map(|p| p.kind()) { @@ -2391,9 +2395,67 @@ fn populate_message_view( _ => false, }; + // Sometimes we need to call this up-front, so we save the result in this variable + // to avoid having to call it twice. + let mut set_username_and_get_avatar_retval = None; + let (item, used_cached_item) = match message.msgtype() { - MessageType::Text(TextMessageEventContent { body, formatted, .. }) - | MessageType::Notice(NoticeMessageEventContent { body, formatted, .. }) => { + MessageType::Text(TextMessageEventContent { body, formatted, .. }) => { + let template = if use_compact_view { + live_id!(CondensedMessage) + } else { + live_id!(Message) + }; + let (item, existed) = list.item_with_existed(cx, item_id, template); + if existed && item_drawn_status.content_drawn { + (item, true) + } else { + populate_text_message_content( + &item.html_or_plaintext(id!(content.message)), + &body, + formatted.as_ref(), + ); + new_drawn_status.content_drawn = true; + (item, false) + } + } + // A notice message is just a message sent by an automated bot, + // so we treat it just like a message but use a different font color. + MessageType::Notice(NoticeMessageEventContent { body, formatted, .. }) => { + is_notice = true; + let template = if use_compact_view { + live_id!(CondensedMessage) + } else { + live_id!(Message) + }; + let (item, existed) = list.item_with_existed(cx, item_id, template); + if existed && item_drawn_status.content_drawn { + (item, true) + } else { + let html_or_plaintext_ref = item.html_or_plaintext(id!(content.message)); + html_or_plaintext_ref.apply_over(cx, live!( + html_view = { + html = { + font_color: (MESSAGE_NOTICE_TEXT_COLOR), + draw_normal: { color: (MESSAGE_NOTICE_TEXT_COLOR), } + draw_italic: { color: (MESSAGE_NOTICE_TEXT_COLOR), } + draw_bold: { color: (MESSAGE_NOTICE_TEXT_COLOR), } + draw_bold_italic: { color: (MESSAGE_NOTICE_TEXT_COLOR), } + } + } + )); + populate_text_message_content( + &html_or_plaintext_ref, + &body, + formatted.as_ref(), + ); + new_drawn_status.content_drawn = true; + (item, false) + } + } + // An emote is just like a message but is prepended with the user's name + // to indicate that it's an "action" that the user is performing. + MessageType::Emote(EmoteMessageEventContent { body, formatted, .. }) => { let template = if use_compact_view { live_id!(CondensedMessage) } else { @@ -2403,11 +2465,34 @@ fn populate_message_view( if existed && item_drawn_status.content_drawn { (item, true) } else { + // Draw the profile up front here because we need the username for the emote body. + let (username, profile_drawn) = set_avatar_and_get_username( + cx, + item.avatar(id!(profile.avatar)), + room_id, + event_tl_item.sender(), + event_tl_item.sender_profile(), + event_tl_item.event_id(), + ); + + // Prepend a "* " to the emote body, as suggested by the Matrix spec. + let (body, formatted) = if let Some(fb) = formatted.as_ref() { + ( + Cow::from(&fb.body), + Some(FormattedBody { + format: fb.format.clone(), + body: format!("* {} {}", &username, &fb.body), + }) + ) + } else { + (Cow::from(format!("* {} {}", &username, body)), None) + }; populate_text_message_content( &item.html_or_plaintext(id!(content.message)), &body, formatted.as_ref(), ); + set_username_and_get_avatar_retval = Some((username, profile_drawn)); new_drawn_status.content_drawn = true; (item, false) } @@ -2490,15 +2575,25 @@ fn populate_message_view( new_drawn_status.profile_drawn = true; } else { // log!("\t --> populate_message_view(): DRAWING profile draw for item_id: {item_id}"); - let (username, profile_drawn) = set_avatar_and_get_username( - cx, - item.avatar(id!(profile.avatar)), - room_id, - event_tl_item.sender(), - event_tl_item.sender_profile(), - event_tl_item.event_id(), + let (username, profile_drawn) = set_username_and_get_avatar_retval.unwrap_or_else(|| + set_avatar_and_get_username( + cx, + item.avatar(id!(profile.avatar)), + room_id, + event_tl_item.sender(), + event_tl_item.sender_profile(), + event_tl_item.event_id(), + ) ); - item.label(id!(content.username)).set_text(&username); + let username_label = item.label(id!(content.username)); + if is_notice { + username_label.apply_over(cx, live!( + draw_text: { + color: (MESSAGE_NOTICE_TEXT_COLOR), + } + )); + } + username_label.set_text(&username); new_drawn_status.profile_drawn = profile_drawn; } @@ -2556,7 +2651,7 @@ fn populate_text_message_content( body: &str, formatted_body: Option<&FormattedBody>, ) { - if let Some(formatted_body) = formatted_body + if let Some(formatted_body) = formatted_body.as_ref() .and_then(|fb| (fb.format == MessageFormat::Html).then(|| fb.body.clone())) { message_content_widget.show_html(utils::linkify(formatted_body.as_ref())); diff --git a/src/home/welcome_screen.rs b/src/home/welcome_screen.rs index 5120b59..f51a8cd 100644 --- a/src/home/welcome_screen.rs +++ b/src/home/welcome_screen.rs @@ -29,7 +29,7 @@ live_design! { } // Using the HTML widget to taking advantage of embedding a link within text with proper vertical alignment - { + { padding: {top: 12, left: 0.} font_size: 14. font_color: (WELCOME_TEXT_COLOR) diff --git a/src/shared/html_or_plaintext.rs b/src/shared/html_or_plaintext.rs index aae6229..03c81d3 100644 --- a/src/shared/html_or_plaintext.rs +++ b/src/shared/html_or_plaintext.rs @@ -32,7 +32,7 @@ live_design! { // A centralized widget where we define styles and custom elements for HTML // message content. This is a wrapper around Makepad's built-in `Html` widget. - RobrixHtml = { + MessageHtml = { padding: 0.0, width: Fill, height: Fit, // see comment in `HtmlOrPlaintext` font_size: (MESSAGE_FONT_SIZE), @@ -59,19 +59,13 @@ live_design! { a = { padding: {left: 1.0, right: 1.5}, - // draw_text: { - // text_style: { font_size: (MESSAGE_FONT_SIZE), height_factor: (HTML_TEXT_HEIGHT_FACTOR), line_spacing: (HTML_LINE_SPACING), top_drop: 1.2, }, - // color: #00f, - // color_pressed: #f00, - // color_hover: #0f0, - // } } body: "[ HTML message placeholder]", } // A view container that displays either plaintext s(a simple `Label`) - // or rich HTML content (an instance of `RobrixHtml`). + // or rich HTML content (an instance of `MessageHtml`). // // Key Usage Notes: // * Labels need their width to be Fill *and* all of their parent views @@ -101,7 +95,7 @@ live_design! { html_view = { visible: false, width: Fill, height: Fit, // see above comment - html = {} + html = {} } } } diff --git a/src/shared/styles.rs b/src/shared/styles.rs index 5930926..82097d6 100644 --- a/src/shared/styles.rs +++ b/src/shared/styles.rs @@ -31,7 +31,9 @@ live_design! { TYPING_NOTICE_TEXT_COLOR = #121570 MESSAGE_FONT_SIZE = 11 - MESSAGE_TEXT_COLOR = #x444 + MESSAGE_TEXT_COLOR = #x333 + // notices (automated messages from bots) use a lighter color + MESSAGE_NOTICE_TEXT_COLOR = #x888 MESSAGE_TEXT_LINE_SPACING = 1.35 MESSAGE_TEXT_HEIGHT_FACTOR = 1.55 // This font should only be used for plaintext labels. Don't use this for Html content, @@ -44,6 +46,7 @@ live_design! { MESSAGE_REPLY_PREVIEW_FONT_SIZE = 9.5 + SMALL_STATE_FONT_SIZE = 9.0 SMALL_STATE_TEXT_COLOR = #x888 SMALL_STATE_TEXT_STYLE = {