app/chatview: fix multiline msgs

This commit is contained in:
jkds
2026-01-06 07:43:05 +01:00
parent 74b18df957
commit 9af3c403a7
3 changed files with 185 additions and 90 deletions

View File

@@ -540,8 +540,8 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
menu::make(app, content.clone(), i18n_fish).await;
// @@@ Debug stuff @@@
//let chatview_node = app.sg_root.lookup_node("/window/content/dev_chat_layer").unwrap();
//chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
//let menu_node = app.sg_root.lookup_node("/window/content/menu_layer").unwrap();
//menu_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
let chatview_node = app.sg_root.lookup_node("/window/content/dev_chat_layer").unwrap();
chatview_node.set_property_bool(atom, Role::App, "is_visible", true).unwrap();
let menu_node = app.sg_root.lookup_node("/window/content/menu_layer").unwrap();
menu_node.set_property_bool(atom, Role::App, "is_visible", false).unwrap();
}

View File

@@ -451,7 +451,9 @@ impl ChatView {
t!("Inserting new message");
// Insert the privmsg since it doesn't already exist
let privmsg = msgbuf.insert_privmsg(timest, msg_id.clone(), nick.clone(), text.clone());
let privmsg = msgbuf
.insert_privmsg(timest, msg_id.clone(), nick.clone(), text.clone(), self.rect.get())
.await;
if privmsg.is_none() {
// Not visible so no need to redraw
return
@@ -496,7 +498,11 @@ impl ChatView {
// Add message to page
let mut msgbuf = self.msgbuf.lock().await;
let Some(privmsg) = msgbuf.insert_privmsg(timest, msg_id, nick, text) else { return };
let Some(privmsg) =
msgbuf.insert_privmsg(timest, msg_id, nick, text, self.rect.get()).await
else {
return
};
privmsg.confirmed = false;
let atom = self.render_api.make_guard(gfxtag!("ChatView::handle_insert_unconf_line"));
self.redraw_cached(atom.batch_id, &mut msgbuf).await;
@@ -558,7 +564,7 @@ impl ChatView {
let mut msgbuf = self.msgbuf.lock().await;
let total_height = msgbuf.calc_total_height().await;
let total_height = msgbuf.calc_total_height(&rect).await;
if total_height > top + preload_height {
// Nothing to do here
//t!("bgloader: buffer is sufficient [trace_id={trace_id}]");
@@ -598,12 +604,15 @@ impl ChatView {
let chatmsg: ChatMsg = deserialize(&v).unwrap();
//t!("{timest:?} {chatmsg:?} [trace_id={trace_id}]");
let msg_height = msgbuf.push_privmsg(
timest,
msg_id.clone(),
chatmsg.nick.clone(),
chatmsg.text.clone(),
);
let msg_height = msgbuf
.push_privmsg(
timest,
msg_id.clone(),
chatmsg.nick.clone(),
chatmsg.text.clone(),
&rect,
)
.await;
if let Some(url) = get_file_url(&chatmsg.text) {
msgbuf.insert_filemsg(
@@ -678,7 +687,7 @@ impl ChatView {
return Some(0.)
}
let total_height = msgbuf.calc_total_height().await;
let total_height = msgbuf.calc_total_height(&self.rect.get()).await;
let max_allowed_scroll = if total_height > rect_h { total_height - rect_h } else { 0. };
if scroll > max_allowed_scroll {

View File

@@ -64,6 +64,7 @@ pub struct PrivMessage {
is_selected: bool,
mesh_cache: Option<Vec<DrawInstruction>>,
txt_layout: Option<parley::Layout<Color>>,
}
impl PrivMessage {
@@ -92,6 +93,7 @@ impl PrivMessage {
confirmed: true,
is_selected: false,
mesh_cache: None,
txt_layout: None,
})
}
@@ -102,55 +104,22 @@ impl PrivMessage {
}
fn height(&self, line_height: f32) -> f32 {
// TODO: calculate based on actual layout height
// For now, assume a single line
line_height
self.txt_layout.as_ref().unwrap().len() as f32 * line_height
}
async fn gen_mesh(
fn cache_txt_layout(
&mut self,
txt_ctx: &mut text::TextContext,
clip: &Rectangle,
line_height: f32,
msg_spacing: f32,
timestamp_width: f32,
nick_colors: &[Color],
timestamp_color: Color,
text_color: Color,
hi_bg_color: Color,
render_api: &RenderApi,
) -> Vec<DrawInstruction> {
if let Some(instrs) = &self.mesh_cache {
return instrs.clone()
) {
if self.txt_layout.is_some() {
return
}
let mut all_instrs = vec![DrawInstruction::Move(Point::new(0., -line_height))];
// Draw selection background if selected
if self.is_selected {
let height = self.height(line_height) + msg_spacing;
let mut mesh = MeshBuilder::new(gfxtag!("chatview_privmsg_sel"));
mesh.draw_filled_box(
&Rectangle { x: 0., y: -height, w: clip.w, h: height },
hi_bg_color,
);
all_instrs
.push(DrawInstruction::Draw(mesh.alloc(render_api).draw_with_textures(vec![])));
}
let mut txt_ctx = text::TEXT_CTX.get().await;
// Timestamp layout
let timestr = Self::gen_timestr(self.timestamp);
let timestamp_layout = txt_ctx.make_layout(
&timestr,
timestamp_color,
self.timestamp_font_size,
line_height / self.timestamp_font_size,
self.window_scale,
None,
&[],
);
// Message text layout
let linetext = if self.nick == "NOTICE" {
self.text.clone()
@@ -160,7 +129,7 @@ impl PrivMessage {
let nick_color = select_nick_color(&self.nick, nick_colors);
let text_layout = if self.nick == "NOTICE" {
let txt_layout = if self.nick == "NOTICE" {
txt_ctx.make_layout(
&linetext,
text_color,
@@ -184,6 +153,64 @@ impl PrivMessage {
&[(0..nick_end, nick_color)],
)
};
self.txt_layout = Some(txt_layout);
}
async fn gen_mesh(
&mut self,
clip: &Rectangle,
line_height: f32,
msg_spacing: f32,
timestamp_width: f32,
nick_colors: &[Color],
timestamp_color: Color,
text_color: Color,
hi_bg_color: Color,
render_api: &RenderApi,
) -> Vec<DrawInstruction> {
if let Some(instrs) = &self.mesh_cache {
assert!(self.txt_layout.is_some());
return instrs.clone()
}
let mut txt_ctx = text::TEXT_CTX.get().await;
// Timestamp layout
let timestr = Self::gen_timestr(self.timestamp);
let timestamp_layout = txt_ctx.make_layout(
&timestr,
timestamp_color,
self.timestamp_font_size,
line_height / self.timestamp_font_size,
self.window_scale,
None,
&[],
);
self.cache_txt_layout(
&mut txt_ctx,
clip,
line_height,
timestamp_width,
nick_colors,
text_color,
);
drop(txt_ctx);
let mut all_instrs = vec![];
// Draw selection background if selected
if self.is_selected {
let height = self.height(line_height) + msg_spacing;
let mut mesh = MeshBuilder::new(gfxtag!("chatview_privmsg_sel"));
mesh.draw_filled_box(
&Rectangle { x: 0., y: -height, w: clip.w, h: height },
hi_bg_color,
);
all_instrs
.push(DrawInstruction::Draw(mesh.alloc(render_api).draw_with_textures(vec![])));
}
// Render timestamp
let timestamp_instrs =
@@ -192,8 +219,11 @@ impl PrivMessage {
// Render message text offset by timestamp_width
all_instrs.push(DrawInstruction::Move(Point::new(timestamp_width, 0.)));
let text_instrs =
text::render_layout(&text_layout, render_api, gfxtag!("chatview_privmsg_text"));
let text_instrs = text::render_layout(
self.txt_layout.as_ref().unwrap(),
render_api,
gfxtag!("chatview_privmsg_text"),
);
all_instrs.extend(text_instrs);
self.mesh_cache = Some(all_instrs.clone());
@@ -210,6 +240,7 @@ impl PrivMessage {
fn clear_mesh(&mut self) {
// Auto-deletes when refs are dropped
self.mesh_cache = None;
self.txt_layout = None;
}
fn select(&mut self) {
@@ -288,11 +319,7 @@ impl DateMessage {
);
drop(txt_ctx);
let mut txt_instrs = text::render_layout(&layout, render_api, gfxtag!("chatview_datemsg"));
let mut instrs = Vec::with_capacity(1 + txt_instrs.len());
instrs.push(DrawInstruction::Move(Point::new(0., -line_height)));
instrs.append(&mut txt_instrs);
let mut instrs = text::render_layout(&layout, render_api, gfxtag!("chatview_datemsg"));
// Cache the instructions
self.mesh_cache = Some(instrs.clone());
instrs
@@ -510,10 +537,9 @@ impl FileMessage {
all_instrs
.push(DrawInstruction::Move(Point::new(timestamp_width + Self::BOX_PADDING_X, 0.)));
for layout in layouts {
all_instrs.push(DrawInstruction::Move(Point::new(0., -line_height)));
let instrs = text::render_layout(&layout, render_api, gfxtag!("chatview_filemsg_text"));
all_instrs.extend(instrs);
all_instrs.push(DrawInstruction::Move(Point::new(0., -line_height)));
}
self.mesh_cache = Some(all_instrs.clone());
@@ -626,6 +652,31 @@ impl Message {
}
}
fn cache_txt_layout(
&mut self,
txt_ctx: &mut text::TextContext,
clip: &Rectangle,
line_height: f32,
timestamp_width: f32,
nick_colors: &[Color],
text_color: Color,
) {
match self {
Self::Priv(m) => {
m.cache_txt_layout(
txt_ctx,
clip,
line_height,
timestamp_width,
nick_colors,
text_color,
);
}
Self::Date(_) => {}
Self::File(_) => {}
}
}
async fn gen_mesh(
&mut self,
clip: &Rectangle,
@@ -799,10 +850,13 @@ impl MessageBuffer {
}
}
pub async fn calc_total_height(&mut self) -> f32 {
pub async fn calc_total_height(&mut self, rect: &Rectangle) -> f32 {
let line_height = self.line_height.get();
let baseline = self.baseline.get();
let timestamp_width = self.timestamp_width.get();
let msg_spacing = self.msg_spacing.get();
let text_color = self.text_color.get();
let nick_colors = self.read_nick_colors();
let mut height = 0.;
let msgs = self.msgs_with_date();
@@ -817,6 +871,17 @@ impl MessageBuffer {
height += msg_spacing;
}
let mut txt_ctx = text::TEXT_CTX.get().await;
msg.cache_txt_layout(
&mut txt_ctx,
&rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
drop(txt_ctx);
height += msg.height(line_height);
}
@@ -847,20 +912,24 @@ impl MessageBuffer {
return true
}
pub fn insert_privmsg(
pub async fn insert_privmsg(
&mut self,
timest: Timestamp,
msg_id: MessageId,
nick: String,
text: String,
rect: Rectangle,
) -> Option<&mut PrivMessage> {
t!("insert_privmsg({timest}, {msg_id}, {nick}, {text})");
let line_height = self.line_height.get();
let font_size = self.font_size.get();
let timestamp_font_size = self.timestamp_font_size.get();
let timestamp_width = self.timestamp_width.get();
let window_scale = self.window_scale.get();
let text_color = self.text_color.get();
let nick_colors = self.read_nick_colors();
let msg = PrivMessage::new(
let mut msg = PrivMessage::new(
font_size,
timestamp_font_size,
window_scale,
@@ -870,6 +939,17 @@ impl MessageBuffer {
text,
);
let mut txt_ctx = text::TEXT_CTX.get().await;
msg.cache_txt_layout(
&mut txt_ctx,
&rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
drop(txt_ctx);
if self.msgs.is_empty() {
self.msgs.push(msg);
return self.msgs.last_mut().unwrap().get_privmsg_mut()
@@ -905,20 +985,24 @@ impl MessageBuffer {
return self.msgs[idx].get_privmsg_mut()
}
pub fn push_privmsg(
pub async fn push_privmsg(
&mut self,
timest: Timestamp,
msg_id: MessageId,
nick: String,
text: String,
rect: &Rectangle,
) -> f32 {
//t!("push_privmsg({timest}, {msg_id}, {nick}, {text})");
let line_height = self.line_height.get();
let font_size = self.font_size.get();
let timestamp_font_size = self.timestamp_font_size.get();
let timestamp_width = self.timestamp_width.get();
let window_scale = self.window_scale.get();
let text_color = self.text_color.get();
let nick_colors = self.read_nick_colors();
let msg = PrivMessage::new(
let mut msg = PrivMessage::new(
font_size,
timestamp_font_size,
window_scale,
@@ -928,13 +1012,18 @@ impl MessageBuffer {
text,
);
let mut txt_ctx = text::TEXT_CTX.get().await;
msg.cache_txt_layout(
&mut txt_ctx,
rect,
line_height,
timestamp_width,
&nick_colors,
text_color,
);
drop(txt_ctx);
let msg_height = msg.height(self.line_height.get());
if self.msgs.is_empty() {
self.msgs.push(msg);
return msg_height
}
self.msgs.push(msg);
msg_height
}
@@ -964,19 +1053,6 @@ impl MessageBuffer {
let mut meshes = vec![];
let mut current_pos = 0.;
while let Some(msg) = msgs.next().await {
let mesh_height = msg.height(line_height);
let msg_bottom = current_pos;
let msg_top = current_pos + mesh_height;
if msg_bottom > scroll + rect.h {
break
}
if msg_top < scroll {
current_pos += msg_spacing;
current_pos += mesh_height;
continue
}
let instrs = msg
.gen_mesh(
rect,
@@ -991,10 +1067,20 @@ impl MessageBuffer {
)
.await;
meshes.push((current_pos, instrs));
let mesh_height = msg.height(line_height);
current_pos += msg_spacing + mesh_height;
current_pos += msg_spacing;
current_pos += mesh_height;
let msg_top = current_pos;
let msg_bottom = current_pos - mesh_height;
if msg_bottom > scroll + rect.h {
break
}
if msg_top < scroll {
continue
}
meshes.push((current_pos, instrs));
}
meshes
}