|
|
@@ -34,7 +34,7 @@ use crate::{
|
|
|
prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyUint32, Role},
|
|
|
pubsub::Subscription,
|
|
|
scene::{Pimpl, SceneGraph, SceneGraphPtr2, SceneNodeId},
|
|
|
- text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr},
|
|
|
+ text::{self, glyph_str, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr},
|
|
|
util::enumerate_mut,
|
|
|
ExecutorPtr,
|
|
|
};
|
|
|
@@ -50,7 +50,7 @@ fn is_whitespace(s: &str) -> bool {
|
|
|
}
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
-pub(super) struct Message {
|
|
|
+pub(super) struct PrivMessage {
|
|
|
font_size: f32,
|
|
|
|
|
|
timestamp: Timestamp,
|
|
|
@@ -60,9 +60,12 @@ pub(super) struct Message {
|
|
|
|
|
|
unwrapped_glyphs: Vec<Glyph>,
|
|
|
wrapped_lines: Vec<Vec<Glyph>>,
|
|
|
+
|
|
|
+ atlas: text::RenderedAtlas,
|
|
|
+ mesh_cache: Option<DrawMesh>,
|
|
|
}
|
|
|
|
|
|
-impl Message {
|
|
|
+impl PrivMessage {
|
|
|
pub(super) async fn new(
|
|
|
font_size: f32,
|
|
|
|
|
|
@@ -72,150 +75,86 @@ impl Message {
|
|
|
text: String,
|
|
|
|
|
|
line_width: f32,
|
|
|
+
|
|
|
text_shaper: &TextShaper,
|
|
|
- ) -> Self {
|
|
|
+ render_api: &RenderApi,
|
|
|
+ ) -> Message {
|
|
|
let dt = Local.timestamp_millis_opt(timestamp as i64).unwrap();
|
|
|
let timestr = dt.format("%H:%M").to_string();
|
|
|
|
|
|
let linetext = format!("{} {} {}", timestr, nick, text);
|
|
|
let unwrapped_glyphs = text_shaper.shape(linetext, font_size).await;
|
|
|
|
|
|
- let mut self_ =
|
|
|
- Self { font_size, timestamp, id, nick, text, unwrapped_glyphs, wrapped_lines: vec![] };
|
|
|
- self_.adjust_line_width(line_width);
|
|
|
- self_
|
|
|
- }
|
|
|
-
|
|
|
- fn adjust_line_width(&mut self, line_width: f32) {
|
|
|
- // Invalidate wrapped_glyphs and recalc
|
|
|
- self.wrapped_lines = text::wrap(line_width, self.font_size, &self.unwrapped_glyphs);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-enum PageVisibility {
|
|
|
- Null,
|
|
|
- Invisible,
|
|
|
- EnterTop,
|
|
|
- Visible,
|
|
|
- ExitBottom,
|
|
|
-}
|
|
|
-
|
|
|
-pub(super) struct Page {
|
|
|
- msgs: Vec<Message>,
|
|
|
- atlas: text::RenderedAtlas,
|
|
|
- mesh_cache: Option<DrawMesh>,
|
|
|
- visible: PageVisibility,
|
|
|
-}
|
|
|
-
|
|
|
-impl Page {
|
|
|
- pub(super) async fn new(msgs: Vec<Message>, render_api: &RenderApi) -> Self {
|
|
|
let mut atlas = text::Atlas::new(render_api);
|
|
|
- for msg in &msgs {
|
|
|
- atlas.push(&msg.unwrapped_glyphs);
|
|
|
- }
|
|
|
- let atlas = atlas.make().await.expect("unable to make atlas!");
|
|
|
-
|
|
|
- Self { msgs, atlas, mesh_cache: None, visible: PageVisibility::Null }
|
|
|
- }
|
|
|
-
|
|
|
- fn clear_mesh(&mut self) -> Option<DrawMesh> {
|
|
|
- std::mem::replace(&mut self.mesh_cache, None)
|
|
|
- }
|
|
|
+ atlas.push(&unwrapped_glyphs);
|
|
|
+ let atlas = atlas.make().await.expect("unable to make atlas");
|
|
|
|
|
|
- async fn split(mut msgs: Vec<Message>, render_api: &RenderApi) -> Vec<Self> {
|
|
|
- msgs.sort_unstable_by_key(|msg| msg.timestamp);
|
|
|
- msgs.reverse();
|
|
|
-
|
|
|
- let chunk_size = if msgs.len() > PAGE_SIZE {
|
|
|
- // Round up so we don't get a weird page with a single item
|
|
|
- msgs.len() / 2 + 1
|
|
|
- } else {
|
|
|
- PAGE_SIZE
|
|
|
+ let mut self_ = Self {
|
|
|
+ font_size,
|
|
|
+ timestamp,
|
|
|
+ id,
|
|
|
+ nick,
|
|
|
+ text,
|
|
|
+ unwrapped_glyphs,
|
|
|
+ wrapped_lines: vec![],
|
|
|
+ atlas,
|
|
|
+ mesh_cache: None,
|
|
|
};
|
|
|
-
|
|
|
- // Replace single page with N pages each with chunk_size messages
|
|
|
- let mut new_pages = vec![];
|
|
|
- for page_msgs in msgs.chunks(chunk_size).map(|m| m.to_vec()) {
|
|
|
- debug!(target: "ui::chatview", "PAGE ==========================");
|
|
|
- for msg in &page_msgs {
|
|
|
- debug!(target: "ui::chatview", "{} {:?}", msg.timestamp, msg.text);
|
|
|
- }
|
|
|
- debug!(target: "ui::chatview", "===============================");
|
|
|
-
|
|
|
- let new_page = Page::new(page_msgs, render_api).await;
|
|
|
- new_pages.push(new_page);
|
|
|
- }
|
|
|
- assert!(!new_pages.is_empty());
|
|
|
- assert!(new_pages.len() <= 2);
|
|
|
- new_pages
|
|
|
+ self_.adjust_width(line_width);
|
|
|
+ Message::Priv(self_)
|
|
|
}
|
|
|
|
|
|
- fn wrapped_lines_count(&self) -> usize {
|
|
|
- let mut count = 0;
|
|
|
- for msg in &self.msgs {
|
|
|
- count += msg.wrapped_lines.len();
|
|
|
- }
|
|
|
- count
|
|
|
+ fn height(&self, line_height: f32) -> f32 {
|
|
|
+ self.wrapped_lines.len() as f32 * line_height
|
|
|
}
|
|
|
|
|
|
async fn gen_mesh(
|
|
|
&mut self,
|
|
|
- render_api: &RenderApi,
|
|
|
clip: &Rectangle,
|
|
|
- font_size: f32,
|
|
|
line_height: f32,
|
|
|
baseline: f32,
|
|
|
nick_colors: &[Color],
|
|
|
timestamp_color: Color,
|
|
|
text_color: Color,
|
|
|
debug_render: bool,
|
|
|
+ render_api: &RenderApi,
|
|
|
) -> DrawMesh {
|
|
|
- if let Some(mesh) = &self.mesh_cache {
|
|
|
- return mesh.clone()
|
|
|
- }
|
|
|
-
|
|
|
- let mut line_idx = 0;
|
|
|
+ //debug!(target: "ui::chatview", "gen_mesh({})", glyph_str(&self.unwrapped_glyphs));
|
|
|
let mut mesh = MeshBuilder::new();
|
|
|
|
|
|
- for msg in &self.msgs {
|
|
|
- let nick_color = select_nick_color(&msg.nick, nick_colors);
|
|
|
-
|
|
|
- let last_idx = msg.wrapped_lines.len() - 1;
|
|
|
- for (i, line) in msg.wrapped_lines.iter().rev().enumerate() {
|
|
|
- let off_y = (line_idx + 1) as f32 * line_height;
|
|
|
- let is_last_line = i == last_idx;
|
|
|
-
|
|
|
- // debug draw baseline
|
|
|
- if debug_render {
|
|
|
- let y = baseline - off_y;
|
|
|
- mesh.draw_filled_box(
|
|
|
- &Rectangle { x: 0., y: y - 1., w: clip.w, h: 1. },
|
|
|
- COLOR_BLUE,
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- self.render_line(
|
|
|
- &mut mesh,
|
|
|
- line,
|
|
|
- off_y,
|
|
|
- is_last_line,
|
|
|
- font_size,
|
|
|
- baseline,
|
|
|
- timestamp_color,
|
|
|
- nick_color,
|
|
|
- text_color,
|
|
|
- debug_render,
|
|
|
- );
|
|
|
+ let nick_color = select_nick_color(&self.nick, nick_colors);
|
|
|
|
|
|
- line_idx += 1;
|
|
|
+ let last_idx = self.wrapped_lines.len() - 1;
|
|
|
+ for (i, line) in self.wrapped_lines.iter().rev().enumerate() {
|
|
|
+ let off_y = (i + 1) as f32 * line_height;
|
|
|
+ let is_last_line = i == last_idx;
|
|
|
+
|
|
|
+ // debug draw baseline
|
|
|
+ if debug_render {
|
|
|
+ let y = baseline - off_y;
|
|
|
+ mesh.draw_filled_box(&Rectangle { x: 0., y: y - 1., w: clip.w, h: 1. }, COLOR_BLUE);
|
|
|
}
|
|
|
+
|
|
|
+ self.render_line(
|
|
|
+ &mut mesh,
|
|
|
+ line,
|
|
|
+ off_y,
|
|
|
+ is_last_line,
|
|
|
+ baseline,
|
|
|
+ timestamp_color,
|
|
|
+ nick_color,
|
|
|
+ text_color,
|
|
|
+ debug_render,
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
- // debug draw page outline
|
|
|
if debug_render {
|
|
|
- let height = line_idx as f32 * line_height;
|
|
|
- let page_rect = Rectangle::from_array([0., -height, clip.w, height]);
|
|
|
- mesh.draw_outline(&page_rect, COLOR_PINK, 1.);
|
|
|
+ let height = self.height(line_height);
|
|
|
+ mesh.draw_outline(
|
|
|
+ &Rectangle { x: 0., y: -height, w: clip.w, h: height },
|
|
|
+ COLOR_PINK,
|
|
|
+ 1.,
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
let mesh = mesh.alloc(render_api).await.unwrap();
|
|
|
@@ -231,13 +170,13 @@ impl Page {
|
|
|
line: &Vec<Glyph>,
|
|
|
off_y: f32,
|
|
|
is_last: bool,
|
|
|
- font_size: f32,
|
|
|
baseline: f32,
|
|
|
timestamp_color: Color,
|
|
|
nick_color: Color,
|
|
|
text_color: Color,
|
|
|
debug_render: bool,
|
|
|
) {
|
|
|
+ //debug!(target: "ui::chatview", "render_line({})", glyph_str(line));
|
|
|
// Keep track of the 'section'
|
|
|
// Section 0 is the timestamp
|
|
|
// Section 1 is the nickname (colorized)
|
|
|
@@ -247,7 +186,7 @@ impl Page {
|
|
|
section = 0;
|
|
|
}
|
|
|
|
|
|
- let glyph_pos_iter = GlyphPositionIter::new(font_size, line, baseline);
|
|
|
+ let glyph_pos_iter = GlyphPositionIter::new(self.font_size, line, baseline);
|
|
|
for (mut glyph_rect, glyph) in glyph_pos_iter.zip(line.iter()) {
|
|
|
let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
|
|
|
glyph_rect.y -= off_y;
|
|
|
@@ -269,6 +208,177 @@ impl Page {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ fn clear_mesh(&mut self) -> Option<DrawMesh> {
|
|
|
+ std::mem::replace(&mut self.mesh_cache, None)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn adjust_width(&mut self, line_width: f32) {
|
|
|
+ // Invalidate wrapped_glyphs and recalc
|
|
|
+ self.wrapped_lines = text::wrap(line_width, self.font_size, &self.unwrapped_glyphs);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[derive(Clone)]
|
|
|
+pub(super) struct DateMessage {
|
|
|
+ font_size: f32,
|
|
|
+ timestamp: Timestamp,
|
|
|
+ glyphs: Vec<Glyph>,
|
|
|
+
|
|
|
+ atlas: text::RenderedAtlas,
|
|
|
+ mesh_cache: Option<DrawMesh>,
|
|
|
+}
|
|
|
+
|
|
|
+impl DateMessage {
|
|
|
+ pub(super) async fn new(
|
|
|
+ font_size: f32,
|
|
|
+ timestamp: Timestamp,
|
|
|
+
|
|
|
+ text_shaper: &TextShaper,
|
|
|
+ render_api: &RenderApi,
|
|
|
+ ) -> Message {
|
|
|
+ let dt = Local.timestamp_millis_opt(timestamp as i64).unwrap();
|
|
|
+ let datestr = dt.format("%a %-d %b %Y").to_string();
|
|
|
+
|
|
|
+ let dt2 = dt.date_naive().and_hms_opt(0, 0, 0).unwrap();
|
|
|
+ assert_eq!(dt.date_naive(), dt2.date());
|
|
|
+ let timestamp = Local.from_local_datetime(&dt2).unwrap().timestamp_millis() as u64;
|
|
|
+
|
|
|
+ let glyphs = text_shaper.shape(datestr, font_size).await;
|
|
|
+
|
|
|
+ let mut atlas = text::Atlas::new(render_api);
|
|
|
+ atlas.push(&glyphs);
|
|
|
+ let atlas = atlas.make().await.expect("unable to make atlas");
|
|
|
+
|
|
|
+ Message::Date(Self { font_size, timestamp, glyphs, atlas, mesh_cache: None })
|
|
|
+ }
|
|
|
+
|
|
|
+ fn clear_mesh(&mut self) -> Option<DrawMesh> {
|
|
|
+ None
|
|
|
+ }
|
|
|
+
|
|
|
+ fn adjust_width(&mut self, line_width: f32) {
|
|
|
+ // Do nothing
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn gen_mesh(
|
|
|
+ &mut self,
|
|
|
+ clip: &Rectangle,
|
|
|
+ line_height: f32,
|
|
|
+ baseline: f32,
|
|
|
+ nick_colors: &[Color],
|
|
|
+ timestamp_color: Color,
|
|
|
+ text_color: Color,
|
|
|
+ debug_render: bool,
|
|
|
+ render_api: &RenderApi,
|
|
|
+ ) -> DrawMesh {
|
|
|
+ let mut mesh = MeshBuilder::new();
|
|
|
+
|
|
|
+ let glyph_pos_iter = GlyphPositionIter::new(self.font_size, &self.glyphs, baseline);
|
|
|
+ for (mut glyph_rect, glyph) in glyph_pos_iter.zip(self.glyphs.iter()) {
|
|
|
+ let uv_rect = self.atlas.fetch_uv(glyph.glyph_id).expect("missing glyph UV rect");
|
|
|
+ glyph_rect.y -= line_height;
|
|
|
+ mesh.draw_box(&glyph_rect, timestamp_color, uv_rect);
|
|
|
+ }
|
|
|
+
|
|
|
+ if debug_render {
|
|
|
+ mesh.draw_outline(
|
|
|
+ &Rectangle { x: 0., y: -line_height, w: clip.w, h: line_height },
|
|
|
+ COLOR_PINK,
|
|
|
+ 1.,
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ let mesh = mesh.alloc(render_api).await.unwrap();
|
|
|
+ let mesh = mesh.draw_with_texture(self.atlas.texture_id);
|
|
|
+ self.mesh_cache = Some(mesh.clone());
|
|
|
+
|
|
|
+ mesh
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Easier than fucking around with traits nonsense
|
|
|
+enum Message {
|
|
|
+ Priv(PrivMessage),
|
|
|
+ Date(DateMessage),
|
|
|
+}
|
|
|
+
|
|
|
+impl Message {
|
|
|
+ fn timestamp(&self) -> u64 {
|
|
|
+ match self {
|
|
|
+ Self::Priv(m) => m.timestamp,
|
|
|
+ Self::Date(m) => m.timestamp,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn height(&self, line_height: f32) -> f32 {
|
|
|
+ match self {
|
|
|
+ Self::Priv(m) => m.height(line_height),
|
|
|
+ Self::Date(_) => line_height,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn clear_mesh(&mut self) -> Option<DrawMesh> {
|
|
|
+ match self {
|
|
|
+ Self::Priv(m) => m.clear_mesh(),
|
|
|
+ Self::Date(m) => m.clear_mesh(),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn adjust_width(&mut self, line_width: f32) {
|
|
|
+ match self {
|
|
|
+ Self::Priv(m) => m.adjust_width(line_width),
|
|
|
+ Self::Date(_) => {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn gen_mesh(
|
|
|
+ &mut self,
|
|
|
+ clip: &Rectangle,
|
|
|
+ line_height: f32,
|
|
|
+ baseline: f32,
|
|
|
+ nick_colors: &[Color],
|
|
|
+ timestamp_color: Color,
|
|
|
+ text_color: Color,
|
|
|
+ debug_render: bool,
|
|
|
+ render_api: &RenderApi,
|
|
|
+ ) -> DrawMesh {
|
|
|
+ match self {
|
|
|
+ Self::Priv(m) => {
|
|
|
+ m.gen_mesh(
|
|
|
+ clip,
|
|
|
+ line_height,
|
|
|
+ baseline,
|
|
|
+ nick_colors,
|
|
|
+ timestamp_color,
|
|
|
+ text_color,
|
|
|
+ debug_render,
|
|
|
+ render_api,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ }
|
|
|
+ Self::Date(m) => {
|
|
|
+ m.gen_mesh(
|
|
|
+ clip,
|
|
|
+ line_height,
|
|
|
+ baseline,
|
|
|
+ nick_colors,
|
|
|
+ timestamp_color,
|
|
|
+ text_color,
|
|
|
+ debug_render,
|
|
|
+ render_api,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn is_date(&self) -> bool {
|
|
|
+ match self {
|
|
|
+ Self::Priv(m) => false,
|
|
|
+ Self::Date(_) => true,
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
fn select_nick_color(nick: &str, nick_colors: &[Color]) -> Color {
|
|
|
@@ -295,35 +405,34 @@ impl FreedData {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub struct PageManager {
|
|
|
- pages: Vec<Page>,
|
|
|
+pub struct MessageBuffer {
|
|
|
+ /// From most recent to older
|
|
|
+ msgs: Vec<Message>,
|
|
|
pub(super) freed: FreedData,
|
|
|
pub(super) line_width: f32,
|
|
|
render_api: RenderApiPtr,
|
|
|
text_shaper: TextShaperPtr,
|
|
|
}
|
|
|
|
|
|
-impl PageManager {
|
|
|
+impl MessageBuffer {
|
|
|
pub(super) fn new(render_api: RenderApiPtr, text_shaper: TextShaperPtr) -> Self {
|
|
|
- Self { pages: vec![], freed: Default::default(), line_width: 0., render_api, text_shaper }
|
|
|
+ Self { msgs: vec![], freed: Default::default(), line_width: 0., render_api, text_shaper }
|
|
|
}
|
|
|
|
|
|
- pub(super) fn push(&mut self, page: Page) {
|
|
|
- self.pages.push(page);
|
|
|
+ pub(super) fn push(&mut self, msg: Message) {
|
|
|
+ self.msgs.push(msg);
|
|
|
}
|
|
|
|
|
|
/// For scrolling we want to be able to adjust and measure without
|
|
|
/// explicitly rendering since it may be off screen.
|
|
|
- pub(super) fn adjust_line_width(&mut self, line_width: f32) {
|
|
|
+ pub(super) fn adjust_width(&mut self, line_width: f32) {
|
|
|
if (line_width - self.line_width).abs() < f32::EPSILON {
|
|
|
return;
|
|
|
}
|
|
|
self.line_width = line_width;
|
|
|
|
|
|
- for page in &mut self.pages {
|
|
|
- for msg in &mut page.msgs {
|
|
|
- msg.adjust_line_width(line_width);
|
|
|
- }
|
|
|
+ for msg in &mut self.msgs {
|
|
|
+ msg.adjust_width(line_width);
|
|
|
}
|
|
|
|
|
|
self.clear_meshes();
|
|
|
@@ -331,26 +440,24 @@ impl PageManager {
|
|
|
|
|
|
/// Clear all meshes and caches. Returns data that needs to be freed.
|
|
|
fn clear_meshes(&mut self) {
|
|
|
- for page in &mut self.pages {
|
|
|
- if let Some(mesh) = page.clear_mesh() {
|
|
|
+ for msg in &mut self.msgs {
|
|
|
+ if let Some(mesh) = msg.clear_mesh() {
|
|
|
self.freed.add_mesh(mesh);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
pub(super) fn calc_total_height(&self, line_height: f32, baseline: f32) -> f32 {
|
|
|
- let descent = line_height - baseline;
|
|
|
- let mut height = descent;
|
|
|
+ let mut height = 0.;
|
|
|
|
|
|
- for page in &self.pages {
|
|
|
- let lines_count = page.wrapped_lines_count();
|
|
|
- height += lines_count as f32 * line_height;
|
|
|
+ for msg in &self.msgs {
|
|
|
+ height += msg.height(line_height);
|
|
|
}
|
|
|
|
|
|
height
|
|
|
}
|
|
|
|
|
|
- pub(super) async fn insert_line(
|
|
|
+ pub(super) async fn insert_privmsg(
|
|
|
&mut self,
|
|
|
font_size: f32,
|
|
|
timest: Timestamp,
|
|
|
@@ -358,7 +465,7 @@ impl PageManager {
|
|
|
nick: String,
|
|
|
text: String,
|
|
|
) {
|
|
|
- let msg = Message::new(
|
|
|
+ let msg = PrivMessage::new(
|
|
|
font_size,
|
|
|
timest,
|
|
|
message_id,
|
|
|
@@ -366,34 +473,33 @@ impl PageManager {
|
|
|
text,
|
|
|
self.line_width,
|
|
|
&self.text_shaper,
|
|
|
+ &self.render_api,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
- // Now add message to page
|
|
|
+ if self.msgs.is_empty() {
|
|
|
+ let datemsg =
|
|
|
+ DateMessage::new(font_size, timest, &self.text_shaper, &self.render_api).await;
|
|
|
+ assert!(datemsg.timestamp() <= timest);
|
|
|
|
|
|
- // Create our very first page
|
|
|
- if self.pages.is_empty() {
|
|
|
- let page = Page::new(vec![msg], &self.render_api).await;
|
|
|
- self.pages.push(page);
|
|
|
- return;
|
|
|
+ self.msgs.push(msg);
|
|
|
+ self.msgs.push(datemsg);
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// We only add lines inside pages.
|
|
|
// Calling the appropriate draw() function after should preload any missing pages.
|
|
|
// When a line is before the first page, it will get preloaded as a new page.
|
|
|
- let first_timest = self.pages.first().unwrap().msgs.last().unwrap().timestamp;
|
|
|
- if timest < first_timest {
|
|
|
+ let oldest_timest = self.oldest_timestamp().unwrap();
|
|
|
+ if timest < oldest_timest {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- let mut idx = None;
|
|
|
- for (i, page) in enumerate_mut(&mut self.pages) {
|
|
|
- //let first_timest = page.msgs.last().unwrap().timestamp;
|
|
|
- let last_timest = page.msgs.first().unwrap().timestamp;
|
|
|
+ // Timestamps go from most recent backwards
|
|
|
|
|
|
- //debug!(target: "ui::chatview", "page {i} [{first_timest}, {last_timest}]");
|
|
|
- if timest <= last_timest {
|
|
|
- //debug!(target: "ui::chatview", "found page {i} [{first_timest}, {last_timest}]");
|
|
|
+ let mut idx = None;
|
|
|
+ for (i, msg) in enumerate_mut(&mut self.msgs) {
|
|
|
+ if timest >= msg.timestamp() {
|
|
|
idx = Some(i);
|
|
|
break
|
|
|
}
|
|
|
@@ -407,29 +513,62 @@ impl PageManager {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- let old_pages_len = self.pages.len();
|
|
|
- // We now want to replace this page by 1 or 2 pages
|
|
|
- // Split pages into 3 parts: head, replaced page and tail
|
|
|
- let mut head = std::mem::replace(&mut self.pages, vec![]);
|
|
|
- let mut drain_iter = head.drain(idx..);
|
|
|
- // Drop the item at idx which will be replaced
|
|
|
- let old_page = drain_iter.next().unwrap();
|
|
|
- let mut tail: Vec<_> = drain_iter.collect();
|
|
|
- assert_eq!(old_pages_len, head.len() + 1 + tail.len());
|
|
|
-
|
|
|
- // Free texture and mesh before dropping page
|
|
|
- if let Some(mesh) = old_page.mesh_cache {
|
|
|
- self.freed.add_mesh(mesh);
|
|
|
- self.freed.add_texture(old_page.atlas.texture_id);
|
|
|
+ self.insert_msg(msg, idx, font_size).await;
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(super) async fn push_privmsg(
|
|
|
+ &mut self,
|
|
|
+ font_size: f32,
|
|
|
+ timest: Timestamp,
|
|
|
+ message_id: MessageId,
|
|
|
+ nick: String,
|
|
|
+ text: String,
|
|
|
+ ) {
|
|
|
+ let msg = PrivMessage::new(
|
|
|
+ font_size,
|
|
|
+ timest,
|
|
|
+ message_id,
|
|
|
+ nick,
|
|
|
+ text,
|
|
|
+ self.line_width,
|
|
|
+ &self.text_shaper,
|
|
|
+ &self.render_api,
|
|
|
+ )
|
|
|
+ .await;
|
|
|
+
|
|
|
+ if self.msgs.is_empty() {
|
|
|
+ let datemsg =
|
|
|
+ DateMessage::new(font_size, timest, &self.text_shaper, &self.render_api).await;
|
|
|
+ assert!(datemsg.timestamp() <= timest);
|
|
|
+
|
|
|
+ self.msgs.push(msg);
|
|
|
+ self.msgs.push(datemsg);
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- let mut msgs = old_page.msgs;
|
|
|
- msgs.push(msg);
|
|
|
- let mut new_pages = Page::split(msgs, &self.render_api).await;
|
|
|
+ let idx = self.msgs.len() - 1;
|
|
|
+ self.insert_msg(msg, idx, font_size).await;
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn insert_msg(&mut self, msg: Message, idx: usize, font_size: f32) {
|
|
|
+ let timest = msg.timestamp();
|
|
|
+ let msg_is_date = msg.is_date();
|
|
|
+
|
|
|
+ // Do we cross from a new day?
|
|
|
+ let prev_msg = &self.msgs[idx];
|
|
|
+ let prev_timest = prev_msg.timestamp();
|
|
|
+ let prev_is_date = prev_msg.is_date();
|
|
|
+ let prev_date = Local.timestamp_millis_opt(prev_timest as i64).unwrap().date_naive();
|
|
|
+ let curr_date = Local.timestamp_millis_opt(timest as i64).unwrap().date_naive();
|
|
|
|
|
|
- self.pages.append(&mut head);
|
|
|
- self.pages.append(&mut new_pages);
|
|
|
- self.pages.append(&mut tail);
|
|
|
+ self.msgs.insert(idx, msg);
|
|
|
+
|
|
|
+ if !prev_is_date && !msg_is_date && prev_date != curr_date {
|
|
|
+ let datemsg =
|
|
|
+ DateMessage::new(font_size, timest, &self.text_shaper, &self.render_api).await;
|
|
|
+ assert!(datemsg.timestamp() <= timest);
|
|
|
+ self.msgs.insert(idx + 1, datemsg);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// Generate caches and return meshes
|
|
|
@@ -449,27 +588,25 @@ impl PageManager {
|
|
|
|
|
|
let descent = line_height - baseline;
|
|
|
let mut current_height = descent;
|
|
|
- for page in &mut self.pages {
|
|
|
+ for msg in &mut self.msgs {
|
|
|
if current_height > scroll + rect.h {
|
|
|
break
|
|
|
}
|
|
|
|
|
|
- let mesh = page
|
|
|
+ let mesh = msg
|
|
|
.gen_mesh(
|
|
|
- &self.render_api,
|
|
|
rect,
|
|
|
- font_size,
|
|
|
line_height,
|
|
|
baseline,
|
|
|
nick_colors,
|
|
|
timestamp_color,
|
|
|
text_color,
|
|
|
debug_render,
|
|
|
+ &self.render_api,
|
|
|
)
|
|
|
.await;
|
|
|
|
|
|
- let lines_count = page.wrapped_lines_count();
|
|
|
- let mesh_height = lines_count as f32 * line_height;
|
|
|
+ let mesh_height = msg.height(line_height);
|
|
|
|
|
|
meshes.push((mesh_height, mesh));
|
|
|
|
|
|
@@ -479,10 +616,13 @@ impl PageManager {
|
|
|
meshes
|
|
|
}
|
|
|
|
|
|
- pub(super) fn last_timestamp(&self) -> Option<Timestamp> {
|
|
|
- let last_page_msgs = &self.pages.last()?.msgs;
|
|
|
- // There should be no pages with 0 messages. We can unwrap here.
|
|
|
- debug!(target: "ui::chatview", "last page has {} msgs", last_page_msgs.len());
|
|
|
- Some(last_page_msgs.last().unwrap().timestamp)
|
|
|
+ pub(super) fn oldest_timestamp(&self) -> Option<Timestamp> {
|
|
|
+ let last_msg = &self.msgs.last()?;
|
|
|
+ Some(last_msg.timestamp())
|
|
|
+ }
|
|
|
+
|
|
|
+ pub(super) fn latest_timestamp(&self) -> Option<Timestamp> {
|
|
|
+ let first_msg = &self.msgs.first()?;
|
|
|
+ Some(first_msg.timestamp())
|
|
|
}
|
|
|
}
|