app/chatview: migrate DateMessage to text2 API

This commit is contained in:
jkds
2026-01-04 18:44:58 +01:00
parent d43a456f7b
commit 3564bd1f84
9 changed files with 195 additions and 191 deletions

View File

@@ -22,7 +22,7 @@ DEBUG_FEATURES = --features=enable-filelog,enable-plugins
#DEV_FEATURES = --features=enable-filelog,enable-netdebug,emulate-android
#DEV_FEATURES = --features=enable-filelog,enable-netdebug,enable-plugins
DEV_FEATURES = --features=enable-plugins
DEV_FEATURES = --features=schema-test
default: build-release
./darkfi-app
@@ -82,7 +82,7 @@ assets/forest_720x1280.mp4:
# Developer targets
dev: $(SRC) fonts assets/forest_1920x1080.ivf
$(CARGO) lbuild $(DEV_FEATURES)
$(CARGO) lbuild --no-default-features $(DEV_FEATURES)
-mv target/debug/darkfi-app .
./darkfi-app

View File

@@ -260,37 +260,3 @@ impl App {
});
}
}
// Just for testing
#[allow(dead_code)]
fn populate_tree(tree: &sled::Tree) {
let chat_txt = include_str!("../../data/chat.txt");
for line in chat_txt.lines() {
let parts: Vec<&str> = line.splitn(3, ' ').collect();
assert_eq!(parts.len(), 3);
let time_parts: Vec<&str> = parts[0].splitn(2, ':').collect();
let (hour, min) = (time_parts[0], time_parts[1]);
let hour = hour.parse::<u32>().unwrap();
let min = min.parse::<u32>().unwrap();
let dt: NaiveDateTime =
NaiveDate::from_ymd_opt(2024, 8, 6).unwrap().and_hms_opt(hour, min, 0).unwrap();
let timest = dt.and_utc().timestamp_millis() as u64;
let nick = parts[1].to_string();
let text = parts[2].to_string();
// serial order is important here
let timest = timest.to_be_bytes();
assert_eq!(timest.len(), 8);
let mut key = [0u8; 8 + 32];
key[..8].clone_from_slice(&timest);
let msg = chatview::ChatMsg { nick, text };
let mut val = vec![];
msg.encode(&mut val).unwrap();
tree.insert(&key, val).unwrap();
}
// O(n)
d!("populated db with {} lines", tree.len());
}

View File

@@ -1604,3 +1604,38 @@ pub async fn make(
});
app.tasks.lock().unwrap().push(editz_text_task);
}
// Just for testing
#[allow(dead_code)]
pub(super) fn populate_tree(tree: &sled::Tree) {
use chrono::{NaiveDate, NaiveDateTime};
let chat_txt = include_str!("../../../data/chat.txt");
for line in chat_txt.lines() {
let parts: Vec<&str> = line.splitn(3, ' ').collect();
assert_eq!(parts.len(), 3);
let time_parts: Vec<&str> = parts[0].splitn(2, ':').collect();
let (hour, min) = (time_parts[0], time_parts[1]);
let hour = hour.parse::<u32>().unwrap();
let min = min.parse::<u32>().unwrap();
let dt: NaiveDateTime =
NaiveDate::from_ymd_opt(2024, 8, 6).unwrap().and_hms_opt(hour, min, 0).unwrap();
let timest = dt.and_utc().timestamp_millis() as u64;
let nick = parts[1].to_string();
let text = parts[2].to_string();
// serial order is important here
let timest = timest.to_be_bytes();
assert_eq!(timest.len(), 8);
let mut key = [0u8; 8 + 32];
key[..8].clone_from_slice(&timest);
let msg = chatview::ChatMsg { nick, text };
let mut val = vec![];
msg.encode(&mut val).unwrap();
tree.insert(&key, val).unwrap();
}
// O(n)
debug!(target: "app::schema", "populated db with {} lines", tree.len());
}

View File

@@ -16,33 +16,37 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use sled_overlay::sled;
use crate::{
app::{
node::{
create_layer, create_singleline_edit, create_text, create_vector_art, create_video,
create_chatview, create_layer, create_singleline_edit, create_text, create_vector_art,
create_video,
},
App,
},
expr,
expr::{self, Compiler},
mesh::COLOR_PURPLE,
prop::{PropertyAtomicGuard, PropertyFloat32, Role},
scene::SceneNodePtr,
ui::{BaseEdit, BaseEditType, Layer, Text, VectorArt, VectorShape, Video},
ui::{BaseEdit, BaseEditType, ChatView, Layer, Text, VectorArt, VectorShape, Video},
util::i18n::I18nBabelFish,
};
use super::chat::populate_tree;
const LIGHTMODE: bool = false;
#[cfg(target_os = "android")]
mod ui_consts {
//pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/";
pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/";
//pub const KING_PATH: &str = "king.png";
pub const VID_PATH: &str = "forest_720x1280.mp4";
}
#[cfg(not(target_os = "android"))]
mod ui_consts {
//pub const CHATDB_PATH: &str = "chatdb";
pub const CHATDB_PATH: &str = "chatdb";
//pub const KING_PATH: &str = "assets/king.png";
pub const VID_PATH: &str = "assets/forest_1920x1080.ivf";
}
@@ -61,6 +65,8 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
)
.unwrap();
let mut cc = Compiler::new();
// Create a layer called view
let layer_node = create_layer("view");
let prop = layer_node.get_property("rect").unwrap();
@@ -226,6 +232,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
*/
// Create KING GNU!
/*
let node = create_video("king");
let prop = node.get_property("rect").unwrap();
prop.set_f32(atom, Role::App, 0, 80.).unwrap();
@@ -236,6 +243,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
let node = node.setup(|me| Video::new(me, app.render_api.clone(), app.ex.clone())).await;
layer_node.link(node);
*/
// Create some text
let node = create_text("label");
@@ -261,13 +269,13 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
prop.set_f32(atom, Role::App, 2, 0.).unwrap();
prop.set_f32(atom, Role::App, 3, 1.).unwrap();
node.set_property_u32(atom, Role::App, "z_index", 1).unwrap();
node.set_property_bool(atom, Role::App, "debug", true).unwrap();
let node = node
.setup(|me| Text::new(me, window_scale.clone(), app.render_api.clone(), i18n_fish.clone()))
.await;
layer_node.link(node);
/*
// ChatView
let node = create_chatview("chatty");
let prop = node.get_property("rect").unwrap();
@@ -318,7 +326,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
1.00, 0.30, 0.00, 1.
];
for c in nick_colors {
prop.push_f32(Role::App, c).unwrap();
prop.push_f32(atom, Role::App, c).unwrap();
}
let prop = node.get_property("hi_bg_color").unwrap();
@@ -348,13 +356,13 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
window_scale.clone(),
app.render_api.clone(),
app.text_shaper.clone(),
app.ex.clone(),
app.sg_root.clone(),
)
})
.await;
layer_node.link(node);
*/
/*
// Text edit
let node = create_singleline_edit("editz");
//let node = create_multiline_edit("editz");
@@ -449,4 +457,5 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
node.call_method("focus", vec![]).await.unwrap();
});
app.tasks.lock().unwrap().push(focus_task);
*/
}

View File

@@ -115,7 +115,7 @@ pub fn setup_logging() -> Option<WorkerGuard> {
#[cfg(not(target_os = "android"))]
{
let terminal_layer = tracing_subscriber::fmt::Layer::new()
.with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE)
//.with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE)
.event_format(EventFormatter::new(true, true))
.fmt_fields(tracing_subscriber::fmt::format::debug_fn(
darkfi::util::logger::terminal_field_formatter,

View File

@@ -78,6 +78,7 @@ pub fn render_layout_with_opts(
}
}
}
// Render the atlas
let atlas = atlas.make();

View File

@@ -460,22 +460,24 @@ impl ChatView {
return
}
#[cfg(feature = "enable-plugins")]
if let Some(url) = get_file_url(&text) {
if let Some(fud) = self.sg_root.lookup_node("/plugin/fud") {
msgbuf.insert_filemsg(
timest,
msg_id,
FileMessageStatus::Initializing,
nick,
url.clone(),
);
// This is incorrect. Scenegraph paths should not be hardcoded in widgets
// nor should there be dependencies on other widgets.
// Instead use signals and slots through app layer. See how focus is done with
// the edit widgets.
let fud = self.sg_root.lookup_node("/plugin/fud").unwrap();
msgbuf.insert_filemsg(
timest,
msg_id,
FileMessageStatus::Initializing,
nick,
url.clone(),
);
let mut data = vec![];
url.encode(&mut data).unwrap();
fud.call_method("get", data).await.unwrap();
}
} else {
error!(target: "ui::chatview", "Fud plugin has not been loaded");
let mut data = vec![];
url.encode(&mut data).unwrap();
fud.call_method("get", data).await.unwrap();
}
}
@@ -587,11 +589,6 @@ impl ChatView {
}
};
let Some(fud) = self.sg_root.lookup_node("/plugin/fud") else {
error!(target: "ui::chatview", "Fud plugin has not been loaded");
return
};
let mut do_redraw = false;
for entry in iter {
let Ok((k, v)) = entry else { break };
@@ -609,7 +606,10 @@ impl ChatView {
chatmsg.text.clone(),
);
// See comment in handle_insert_line()
#[cfg(feature = "enable-plugins")]
if let Some(url) = get_file_url(&chatmsg.text) {
let fud = self.sg_root.lookup_node("/plugin/fud").unwrap();
msgbuf.insert_filemsg(
timest,
msg_id,
@@ -634,7 +634,6 @@ impl ChatView {
}
remaining_visible -= msg_height;
}
//t!("do_redraw = {do_redraw} [trace_id={trace_id}]");
if do_redraw {
let atom = self.render_api.make_guard(gfxtag!("ChatView::handle_bgload"));
self.redraw_cached(atom.batch_id, &mut msgbuf).await;
@@ -710,7 +709,7 @@ impl ChatView {
let meshes = msgbuf.gen_meshes(rect, scroll).await;
for (y_pos, mesh) in meshes {
for (y_pos, mut minstrs) in meshes {
// Apply scroll and scissor
// We use the scissor for scrolling
// Because we use the scissor, our actual rect is now rect instead of parent_rect
@@ -720,7 +719,7 @@ impl ChatView {
let pos = Point::from([off_x, off_y]);
instrs.push(DrawInstruction::SetPos(pos));
instrs.push(DrawInstruction::Draw(mesh));
instrs.append(&mut minstrs);
}
instrs

View File

@@ -35,12 +35,13 @@ use url::Url;
use super::{max, MessageId, Timestamp};
use crate::{
gfx::{gfxtag, DrawMesh, ManagedTexturePtr, Rectangle, RenderApi},
gfx::{gfxtag, DrawInstruction, DrawMesh, ManagedTexturePtr, Point, Rectangle, RenderApi},
mesh::{
Color, MeshBuilder, COLOR_BLUE, COLOR_CYAN, COLOR_GREEN, COLOR_PINK, COLOR_RED, COLOR_WHITE,
},
prop::{PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr},
text::{self, Glyph, GlyphPositionIter, TextShaper, TextShaperPtr},
text2,
util::enumerate_mut,
};
@@ -379,34 +380,19 @@ impl std::fmt::Debug for PrivMessage {
pub struct DateMessage {
font_size: f32,
window_scale: f32,
timestamp: Timestamp,
glyphs: Vec<Glyph>,
atlas: text::RenderedAtlas,
mesh_cache: Option<DrawMesh>,
mesh_cache: Option<Vec<DrawInstruction>>,
}
impl DateMessage {
pub fn new(
font_size: f32,
window_scale: f32,
timestamp: Timestamp,
text_shaper: &TextShaper,
render_api: &RenderApi,
_render_api: &RenderApi,
) -> Message {
let datestr = Self::datestr(timestamp);
let timestamp = Self::timest_to_midnight(timestamp);
let glyphs = text_shaper.shape(datestr, font_size, window_scale);
let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_datemsg"));
atlas.push(&glyphs);
let atlas = atlas.make();
Message::Date(Self { font_size, window_scale, timestamp, glyphs, atlas, mesh_cache: None })
Message::Date(Self { font_size, window_scale, timestamp, mesh_cache: None })
}
fn datestr(timestamp: Timestamp) -> String {
@@ -423,66 +409,60 @@ impl DateMessage {
timestamp
}
/// clear_mesh() must be called after this.
fn adjust_params(
&mut self,
font_size: f32,
window_scale: f32,
text_shaper: &TextShaper,
render_api: &RenderApi,
_text_shaper: &TextShaper,
_render_api: &RenderApi,
) {
self.font_size = font_size;
self.window_scale = window_scale;
let datestr = Self::datestr(self.timestamp);
self.glyphs = text_shaper.shape(datestr, font_size, window_scale);
let mut atlas = text::Atlas::new(render_api, gfxtag!("chatview_datemsg"));
atlas.push(&self.glyphs);
self.atlas = atlas.make();
}
//fn adjust_width(&mut self, line_width: f32) { }
fn clear_mesh(&mut self) {
// Auto-deletes when refs are dropped
self.mesh_cache = None;
}
fn gen_mesh(
fn clear_mesh(&mut self) {
self.mesh_cache = None;
}
async fn gen_mesh(
&mut self,
clip: &Rectangle,
_clip: &Rectangle,
line_height: f32,
baseline: f32,
_baseline: f32,
_nick_colors: &[Color],
timestamp_color: Color,
_text_color: Color,
debug_render: bool,
_debug_render: bool,
render_api: &RenderApi,
) -> DrawMesh {
let mut mesh = MeshBuilder::new(gfxtag!("chatview_datemsg"));
let glyph_pos_iter =
GlyphPositionIter::new(self.font_size, self.window_scale, &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);
) -> Vec<DrawInstruction> {
// Return cached mesh if available
if let Some(cache) = &self.mesh_cache {
return cache.clone()
}
if debug_render {
mesh.draw_outline(
&Rectangle { x: 0., y: -line_height, w: clip.w, h: line_height },
COLOR_PINK,
1.,
);
}
let datestr = Self::datestr(self.timestamp);
let mesh = mesh.alloc(render_api);
let mesh = mesh.draw_with_textures(vec![self.atlas.texture.clone()]);
self.mesh_cache = Some(mesh.clone());
let mut txt_ctx = text2::TEXT_CTX.get().await;
let layout = txt_ctx.make_layout(
&datestr,
timestamp_color,
self.font_size,
line_height / self.font_size,
self.window_scale,
None,
&[],
);
drop(txt_ctx);
mesh
let mut txt_instrs = text2::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);
// Cache the instructions
self.mesh_cache = Some(instrs.clone());
instrs
}
}
@@ -854,7 +834,7 @@ impl Message {
}
}
fn gen_mesh(
async fn gen_mesh(
&mut self,
clip: &Rectangle,
line_height: f32,
@@ -867,44 +847,51 @@ impl Message {
hi_bg_color: Color,
debug_render: bool,
render_api: &RenderApi,
) -> Vec<DrawMesh> {
) -> Vec<DrawInstruction> {
match self {
Self::Priv(m) => vec![m.gen_mesh(
clip,
line_height,
msg_spacing,
baseline,
timestamp_width,
nick_colors,
timestamp_color,
text_color,
hi_bg_color,
debug_render,
render_api,
)],
Self::Date(m) => vec![m.gen_mesh(
clip,
line_height,
baseline,
// No timestamp_width
nick_colors,
timestamp_color,
text_color,
// No hi_bg_color since dates can't be highlighted
debug_render,
render_api,
)],
Self::File(m) => m.gen_mesh(
clip,
line_height,
baseline,
timestamp_width,
nick_colors,
timestamp_color,
text_color,
debug_render,
render_api,
),
Self::Priv(m) => {
let mesh = m.gen_mesh(
clip,
line_height,
msg_spacing,
baseline,
timestamp_width,
nick_colors,
timestamp_color,
text_color,
hi_bg_color,
debug_render,
render_api,
);
vec![DrawInstruction::Draw(mesh)]
}
Self::Date(m) => {
m.gen_mesh(
clip,
line_height,
baseline,
nick_colors,
timestamp_color,
text_color,
debug_render,
render_api,
)
.await
}
Self::File(m) => {
let meshes = m.gen_mesh(
clip,
line_height,
baseline,
timestamp_width,
nick_colors,
timestamp_color,
text_color,
debug_render,
render_api,
);
meshes.into_iter().map(DrawInstruction::Draw).collect()
}
}
}
@@ -1221,8 +1208,12 @@ impl MessageBuffer {
msg_height
}
/// Generate caches and return meshes
pub async fn gen_meshes(&mut self, rect: &Rectangle, scroll: f32) -> Vec<(f32, DrawMesh)> {
/// Generate caches and return draw instructions
pub async fn gen_meshes(
&mut self,
rect: &Rectangle,
scroll: f32,
) -> Vec<(f32, Vec<DrawInstruction>)> {
let line_height = self.line_height.get();
let msg_spacing = self.msg_spacing.get();
let baseline = self.baseline.get();
@@ -1255,27 +1246,27 @@ impl MessageBuffer {
continue
}
for mesh in msg.gen_mesh(
rect,
line_height,
msg_spacing,
baseline,
timestamp_width,
&nick_colors,
timest_color,
text_color,
hi_bg_color,
debug_render,
&render_api,
) {
meshes.push((current_pos, mesh));
}
let instrs = msg
.gen_mesh(
rect,
line_height,
msg_spacing,
baseline,
timestamp_width,
&nick_colors,
timest_color,
text_color,
hi_bg_color,
debug_render,
&render_api,
)
.await;
meshes.push((current_pos, instrs));
current_pos += msg_spacing;
current_pos += mesh_height;
}
//t!("gen_meshes() returning {} meshes", meshes.len());
meshes
}
@@ -1364,13 +1355,7 @@ impl MessageBuffer {
let timest = Local.from_local_datetime(&dt).unwrap().timestamp_millis() as u64;
if !self.date_msgs.contains_key(&date) {
let datemsg = DateMessage::new(
font_size,
window_scale,
timest,
&self.text_shaper,
&self.render_api,
);
let datemsg = DateMessage::new(font_size, window_scale, timest, &self.render_api);
self.date_msgs.insert(date, datemsg);
}

View File

@@ -24,6 +24,7 @@ use tracing::instrument;
use crate::{
gfx::{gfxtag, DrawCall, DrawInstruction, Rectangle, RenderApi},
mesh::MeshBuilder,
prop::{
BatchGuardPtr, PropertyAtomicGuard, PropertyBool, PropertyColor, PropertyFloat32,
PropertyRect, PropertyStr, PropertyUint32, Role,
@@ -158,6 +159,14 @@ impl Text {
let mut instrs = vec![DrawInstruction::Move(rect.pos())];
instrs.append(&mut self.regen_mesh().await);
if self.debug.get() {
let rect = self.rect.get().with_zero_pos();
let mut mesh = MeshBuilder::new(gfxtag!("text_debug-rect"));
mesh.draw_outline(&rect, [0., 1., 0., 0.7], 1.);
let mesh = mesh.alloc(&self.render_api).draw_untextured();
instrs.push(DrawInstruction::Draw(mesh));
}
Some(DrawUpdate {
key: self.dc_key,
draw_calls: vec![(