mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
darkwallet: android touch scrolling chatview
This commit is contained in:
@@ -58,7 +58,7 @@ impl Vertex {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Point {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
|
||||
@@ -70,6 +70,11 @@ fn main() {
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default().with_max_level(LevelFilter::Debug).with_tag("darkfi"),
|
||||
);
|
||||
|
||||
let paths = std::fs::read_dir("/data/data/darkfi.darkwallet/").unwrap();
|
||||
for path in paths {
|
||||
debug!("{}", path.unwrap().path().display())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
use darkfi_serial::{deserialize, Decodable, Encodable, SerialDecodable, SerialEncodable};
|
||||
use miniquad::TouchPhase;
|
||||
use rand::{rngs::OsRng, Rng};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
@@ -67,17 +68,37 @@ struct Page {
|
||||
atlas: text2::RenderedAtlas,
|
||||
}
|
||||
|
||||
struct TouchInfo {
|
||||
start_y: f32,
|
||||
start_instant: std::time::Instant,
|
||||
last_y: f32,
|
||||
}
|
||||
|
||||
impl TouchInfo {
|
||||
fn new() -> Self {
|
||||
Self { start_y: 0., start_instant: std::time::Instant::now(), last_y: 0. }
|
||||
}
|
||||
}
|
||||
|
||||
pub type ChatViewPtr = Arc<ChatView>;
|
||||
|
||||
pub struct ChatView {
|
||||
node_id: SceneNodeId,
|
||||
tasks: Vec<smol::Task<()>>,
|
||||
sg: SceneGraphPtr2,
|
||||
render_api: RenderApiPtr,
|
||||
text_shaper: TextShaperPtr,
|
||||
tree: sled::Tree,
|
||||
|
||||
pages: SyncMutex<Vec<Page>>,
|
||||
drawcalls: SyncMutex<Vec<DrawMesh>>,
|
||||
dc_key: u64,
|
||||
|
||||
/// Used for detecting when scrolling view
|
||||
mouse_pos: SyncMutex<Point>,
|
||||
/// Touch scrolling
|
||||
touch_info: SyncMutex<TouchInfo>,
|
||||
|
||||
rect: PropertyPtr,
|
||||
scroll: PropertyFloat32,
|
||||
font_size: PropertyFloat32,
|
||||
@@ -88,14 +109,18 @@ pub struct ChatView {
|
||||
|
||||
impl ChatView {
|
||||
pub async fn new(
|
||||
ex: Arc<smol::Executor<'static>>,
|
||||
sg: SceneGraphPtr2,
|
||||
node_id: SceneNodeId,
|
||||
render_api: RenderApiPtr,
|
||||
event_pub: GraphicsEventPublisherPtr,
|
||||
text_shaper: TextShaperPtr,
|
||||
tree: sled::Tree,
|
||||
) -> Pimpl {
|
||||
debug!(target: "ui::chatview", "ChatView::new()");
|
||||
let scene_graph = sg.lock().await;
|
||||
let node = scene_graph.get_node(node_id).unwrap();
|
||||
let node_name = node.name.clone();
|
||||
|
||||
let rect = node.get_property("rect").expect("ChatView::rect");
|
||||
let scroll = PropertyFloat32::wrap(node, "scroll", 0).unwrap();
|
||||
@@ -106,21 +131,65 @@ impl ChatView {
|
||||
|
||||
drop(scene_graph);
|
||||
|
||||
let self_ = Arc::new_cyclic(|me: &Weak<Self>| Self {
|
||||
node_id,
|
||||
render_api,
|
||||
text_shaper,
|
||||
tree,
|
||||
let self_ = Arc::new_cyclic(|me: &Weak<Self>| {
|
||||
let ev_sub = event_pub.subscribe_mouse_wheel();
|
||||
let me2 = me.clone();
|
||||
let mouse_wheel_task = ex.spawn(async move {
|
||||
loop {
|
||||
Self::process_mouse_wheel(&me2, &ev_sub).await;
|
||||
}
|
||||
});
|
||||
|
||||
pages: SyncMutex::new(Vec::new()),
|
||||
dc_key: OsRng.gen(),
|
||||
let ev_sub = event_pub.subscribe_mouse_move();
|
||||
let me2 = me.clone();
|
||||
let mouse_move_task = ex.spawn(async move {
|
||||
loop {
|
||||
Self::process_mouse_move(&me2, &ev_sub).await;
|
||||
}
|
||||
});
|
||||
|
||||
rect,
|
||||
scroll,
|
||||
font_size,
|
||||
line_height,
|
||||
baseline,
|
||||
z_index,
|
||||
let ev_sub = event_pub.subscribe_touch();
|
||||
let me2 = me.clone();
|
||||
let touch_task = ex.spawn(async move {
|
||||
loop {
|
||||
Self::process_touch(&me2, &ev_sub).await;
|
||||
}
|
||||
});
|
||||
|
||||
let mut on_modify = OnModify::new(ex, node_name, node_id, me.clone());
|
||||
|
||||
//on_modify.when_change(scroll.prop(), Self::scrollview);
|
||||
|
||||
async fn redraw(self_: Arc<ChatView>) {
|
||||
self_.redraw().await;
|
||||
}
|
||||
on_modify.when_change(rect.clone(), redraw);
|
||||
|
||||
let mut tasks = vec![mouse_wheel_task, mouse_move_task, touch_task];
|
||||
tasks.append(&mut on_modify.tasks);
|
||||
|
||||
Self {
|
||||
node_id,
|
||||
tasks,
|
||||
sg,
|
||||
render_api,
|
||||
text_shaper,
|
||||
tree,
|
||||
|
||||
pages: SyncMutex::new(Vec::new()),
|
||||
drawcalls: SyncMutex::new(Vec::new()),
|
||||
dc_key: OsRng.gen(),
|
||||
|
||||
mouse_pos: SyncMutex::new(Point::from([0., 0.])),
|
||||
touch_info: SyncMutex::new(TouchInfo::new()),
|
||||
|
||||
rect,
|
||||
scroll,
|
||||
font_size,
|
||||
line_height,
|
||||
baseline,
|
||||
z_index,
|
||||
}
|
||||
});
|
||||
|
||||
self_.populate().await;
|
||||
@@ -128,6 +197,141 @@ impl ChatView {
|
||||
Pimpl::ChatView(self_)
|
||||
}
|
||||
|
||||
async fn process_mouse_wheel(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) {
|
||||
let Ok((wheel_x, wheel_y)) = ev_sub.receive().await else {
|
||||
debug!(target: "ui::chatview", "Event relayer closed");
|
||||
return
|
||||
};
|
||||
|
||||
let Some(self_) = me.upgrade() else {
|
||||
// Should not happen
|
||||
panic!("self destroyed before mouse_wheel_task was stopped!");
|
||||
};
|
||||
|
||||
self_.handle_mouse_wheel(wheel_x, wheel_y).await;
|
||||
}
|
||||
|
||||
async fn process_mouse_move(me: &Weak<Self>, ev_sub: &Subscription<(f32, f32)>) {
|
||||
let Ok((mouse_x, mouse_y)) = ev_sub.receive().await else {
|
||||
debug!(target: "ui::chatview", "Event relayer closed");
|
||||
return
|
||||
};
|
||||
|
||||
let Some(self_) = me.upgrade() else {
|
||||
// Should not happen
|
||||
panic!("self destroyed before mouse_move_task was stopped!");
|
||||
};
|
||||
|
||||
self_.handle_mouse_move(mouse_x, mouse_y).await;
|
||||
}
|
||||
|
||||
async fn process_touch(me: &Weak<Self>, ev_sub: &Subscription<(TouchPhase, u64, f32, f32)>) {
|
||||
let Ok((phase, id, touch_x, touch_y)) = ev_sub.receive().await else {
|
||||
debug!(target: "ui::chatview", "Event relayer closed");
|
||||
return
|
||||
};
|
||||
|
||||
let Some(self_) = me.upgrade() else {
|
||||
// Should not happen
|
||||
panic!("self destroyed before touch_task was stopped!");
|
||||
};
|
||||
|
||||
self_.handle_touch(phase, id, touch_x, touch_y).await;
|
||||
}
|
||||
|
||||
async fn handle_mouse_wheel(&self, wheel_x: f32, wheel_y: f32) {
|
||||
debug!(target: "ui::chatview", "handle_mouse_wheel({wheel_x}, {wheel_y})");
|
||||
|
||||
let Some(rect) = self.get_cached_world_rect().await else { return };
|
||||
|
||||
let mouse_pos = self.mouse_pos.lock().unwrap().clone();
|
||||
if !rect.contains(&mouse_pos) {
|
||||
debug!(target: "ui::chatview", "not inside rect");
|
||||
return
|
||||
}
|
||||
debug!(target: "ui::chatview", "inside rect");
|
||||
let scroll = self.scroll.get();
|
||||
self.scroll.set(scroll + wheel_y * 50.);
|
||||
// Render will auto update from on_modify hook
|
||||
}
|
||||
|
||||
async fn handle_mouse_move(&self, mouse_x: f32, mouse_y: f32) {
|
||||
//debug!(target: "ui::chatview", "handle_mouse_move({mouse_x}, {mouse_y})");
|
||||
let mut mouse_pos = self.mouse_pos.lock().unwrap();
|
||||
mouse_pos.x = mouse_x;
|
||||
mouse_pos.y = mouse_y;
|
||||
}
|
||||
|
||||
async fn handle_touch(&self, phase: TouchPhase, id: u64, touch_x: f32, touch_y: f32) {
|
||||
// Ignore multi-touch
|
||||
if id != 0 {
|
||||
return
|
||||
}
|
||||
// Simulate mouse events
|
||||
match phase {
|
||||
TouchPhase::Started => {
|
||||
let mut touch_info = self.touch_info.lock().unwrap();
|
||||
touch_info.start_y = touch_y;
|
||||
touch_info.start_instant = std::time::Instant::now();
|
||||
touch_info.last_y = touch_y;
|
||||
}
|
||||
TouchPhase::Moved => {
|
||||
let start_y = {
|
||||
let mut touch_info = self.touch_info.lock().unwrap();
|
||||
touch_info.last_y = touch_y;
|
||||
touch_info.start_y
|
||||
};
|
||||
|
||||
let dist = touch_y - start_y;
|
||||
let scroll = self.scroll.get();
|
||||
// TODO the line selected should be fixed and move exactly that distance
|
||||
// No use of multipliers
|
||||
// TODO we are maybe doing too many updates so make a widget to 'slow down'
|
||||
// how often we move to fixed intervals.
|
||||
// draw a poly shape and eval each line segment.
|
||||
self.scroll.set(scroll + dist*0.05);
|
||||
self.scrollview().await;
|
||||
}
|
||||
TouchPhase::Ended => {
|
||||
// Now calculate scroll acceleration
|
||||
}
|
||||
TouchPhase::Cancelled => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Beware of this method. Here be dragons.
|
||||
/// Possibly racy so we limit it just to mouse stuff (for now).
|
||||
fn cached_rect(&self) -> Rectangle {
|
||||
let Ok(rect) = read_rect(self.rect.clone()) else {
|
||||
panic!("Node bad rect property");
|
||||
};
|
||||
rect
|
||||
}
|
||||
async fn get_parent_rect(&self) -> Option<Rectangle> {
|
||||
let sg = self.sg.lock().await;
|
||||
let node = sg.get_node(self.node_id).unwrap();
|
||||
let Some(parent_rect) = get_parent_rect(&sg, node) else {
|
||||
return None;
|
||||
};
|
||||
drop(sg);
|
||||
Some(parent_rect)
|
||||
}
|
||||
async fn get_cached_world_rect(&self) -> Option<Rectangle> {
|
||||
// NBD if it's slightly wrong
|
||||
let mut rect = self.cached_rect();
|
||||
|
||||
// If layers can be nested and we use offsets for (x, y)
|
||||
// then this will be incorrect for nested layers.
|
||||
// For now we don't allow nesting of layers.
|
||||
let parent_rect = self.get_parent_rect().await?;
|
||||
|
||||
// Offset rect which is now in world coords
|
||||
rect.x += parent_rect.x;
|
||||
rect.y += parent_rect.y;
|
||||
|
||||
Some(rect)
|
||||
}
|
||||
|
||||
async fn populate(&self) {
|
||||
let mut pages = vec![];
|
||||
let mut msgs = vec![];
|
||||
@@ -172,7 +376,80 @@ impl ChatView {
|
||||
*self.pages.lock().unwrap() = pages;
|
||||
}
|
||||
|
||||
async fn regen_mesh(&self, mut clip: Rectangle) -> Vec<DrawInstruction> {
|
||||
/// Basically a version of redraw() where regen_mesh() is never called.
|
||||
/// Instead we use the cached version.
|
||||
async fn scrollview(&self) {
|
||||
debug!(target: "ui::chatview", "scrollview()");
|
||||
let sg = self.sg.lock().await;
|
||||
let node = sg.get_node(self.node_id).unwrap();
|
||||
|
||||
let Some(parent_rect) = get_parent_rect(&sg, node) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(err) = eval_rect(self.rect.clone(), &parent_rect) {
|
||||
panic!("Node {:?} bad rect property: {}", node, err);
|
||||
}
|
||||
|
||||
let Ok(mut rect) = read_rect(self.rect.clone()) else {
|
||||
panic!("Node {:?} bad rect property", node);
|
||||
};
|
||||
|
||||
//let mut drawcalls = self.regen_mesh(rect.clone()).await;
|
||||
|
||||
debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
|
||||
|
||||
// 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
|
||||
let off_x = 0.;
|
||||
// This calc decides whether scroll is in terms of pages or pixels
|
||||
let off_y = (self.scroll.get() + rect.h) / rect.h;
|
||||
let scale_x = 1. / rect.w;
|
||||
let scale_y = 1. / rect.h;
|
||||
let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
|
||||
glam::Mat4::from_scale(glam::Vec3::new(scale_x, scale_y, 1.));
|
||||
|
||||
let mut instrs =
|
||||
vec![DrawInstruction::ApplyViewport(rect), DrawInstruction::ApplyMatrix(model)];
|
||||
let drawcalls = self.drawcalls.lock().unwrap().clone();
|
||||
let mut drawcalls: Vec<_> = drawcalls.into_iter().map(|dc| DrawInstruction::Draw(dc)).collect();
|
||||
instrs.append(&mut drawcalls);
|
||||
|
||||
let draw_calls = vec![(
|
||||
self.dc_key,
|
||||
DrawCall { instrs, dcs: vec![], z_index: self.z_index.get() },
|
||||
)];
|
||||
|
||||
self.render_api.replace_draw_calls(draw_calls).await;
|
||||
|
||||
debug!(target: "ui::chatview", "scrollview done");
|
||||
}
|
||||
|
||||
async fn redraw(&self) {
|
||||
debug!(target: "ui::chatview", "redraw()");
|
||||
let sg = self.sg.lock().await;
|
||||
let node = sg.get_node(self.node_id).unwrap();
|
||||
|
||||
let Some(parent_rect) = get_parent_rect(&sg, node) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(draw_update) = self.draw(&sg, &parent_rect).await else {
|
||||
error!(target: "ui::chatview", "ChatView {:?} failed to draw", node);
|
||||
return;
|
||||
};
|
||||
self.render_api.replace_draw_calls(draw_update.draw_calls).await;
|
||||
debug!(target: "ui::chatview", "replace draw calls done");
|
||||
for buffer_id in draw_update.freed_buffers {
|
||||
self.render_api.delete_buffer(buffer_id);
|
||||
}
|
||||
for texture_id in draw_update.freed_textures {
|
||||
self.render_api.delete_texture(texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
async fn regen_mesh(&self, mut clip: Rectangle) -> Vec<DrawMesh> {
|
||||
let font_size = self.font_size.get();
|
||||
let line_height = self.line_height.get();
|
||||
let baseline = self.baseline.get();
|
||||
@@ -220,12 +497,12 @@ impl ChatView {
|
||||
|
||||
let mesh = mesh.alloc(&self.render_api).await.unwrap();
|
||||
|
||||
draws.push(DrawInstruction::Draw(DrawMesh {
|
||||
draws.push(DrawMesh {
|
||||
vertex_buffer: mesh.vertex_buffer,
|
||||
index_buffer: mesh.index_buffer,
|
||||
texture: Some(page.atlas.texture_id),
|
||||
num_elements: mesh.num_elements,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
if DEBUG_RENDER {
|
||||
@@ -236,12 +513,12 @@ impl ChatView {
|
||||
2.,
|
||||
);
|
||||
let mesh = debug_mesh.alloc(&self.render_api).await.unwrap();
|
||||
draws.push(DrawInstruction::Draw(DrawMesh {
|
||||
draws.push(DrawMesh {
|
||||
vertex_buffer: mesh.vertex_buffer,
|
||||
index_buffer: mesh.index_buffer,
|
||||
texture: None,
|
||||
num_elements: mesh.num_elements,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
draws
|
||||
@@ -260,14 +537,23 @@ impl ChatView {
|
||||
panic!("Node {:?} bad rect property", node);
|
||||
};
|
||||
|
||||
// TODO: Do we need this? Because of the viewport clipping
|
||||
rect.x += parent_rect.x;
|
||||
rect.y += parent_rect.y;
|
||||
|
||||
let mut drawcalls = self.regen_mesh(rect.clone()).await;
|
||||
// TODO: delete old buffers
|
||||
// we need to save the buffers for that
|
||||
let drawcalls = self.regen_mesh(rect.clone()).await;
|
||||
let old_drawcalls = std::mem::replace(&mut *self.drawcalls.lock().unwrap(), drawcalls.clone());
|
||||
let mut drawcalls: Vec<_> = drawcalls.into_iter().map(|dc| DrawInstruction::Draw(dc)).collect();
|
||||
|
||||
let mut freed_textures = vec![];
|
||||
let mut freed_buffers = vec![];
|
||||
for old_dc in old_drawcalls {
|
||||
freed_buffers.push(old_dc.vertex_buffer);
|
||||
freed_buffers.push(old_dc.index_buffer);
|
||||
if let Some(texture_id) = old_dc.texture {
|
||||
freed_textures.push(texture_id);
|
||||
}
|
||||
}
|
||||
|
||||
debug!(target: "ui::chatview", "chatview rect = {:?}", rect);
|
||||
|
||||
@@ -275,7 +561,8 @@ impl ChatView {
|
||||
// We use the scissor for scrolling
|
||||
// Because we use the scissor, our actual rect is now rect instead of parent_rect
|
||||
let off_x = 0.;
|
||||
let off_y = self.scroll.get() + rect.h / rect.h;
|
||||
// This calc decides whether scroll is in terms of pages or pixels
|
||||
let off_y = (self.scroll.get() + rect.h) / rect.h;
|
||||
let scale_x = 1. / rect.w;
|
||||
let scale_y = 1. / rect.h;
|
||||
let model = glam::Mat4::from_translation(glam::Vec3::new(off_x, off_y, 0.)) *
|
||||
|
||||
Reference in New Issue
Block a user