diff --git a/bin/darkwallet/src/scene.rs b/bin/darkwallet/src/scene.rs
index 3caf4109f..da16348f4 100644
--- a/bin/darkwallet/src/scene.rs
+++ b/bin/darkwallet/src/scene.rs
@@ -471,6 +471,7 @@ pub enum Pimpl {
VectorArt(ui::VectorArtPtr),
Text(ui::TextPtr),
EditBox(ui::EditBoxPtr),
+ ChatEdit(ui::ChatEditPtr),
ChatView(ui::ChatViewPtr),
Image(ui::ImagePtr),
Button(ui::ButtonPtr),
diff --git a/bin/darkwallet/src/ui/chatedit.rs b/bin/darkwallet/src/ui/chatedit.rs
new file mode 100644
index 000000000..95bb5131f
--- /dev/null
+++ b/bin/darkwallet/src/ui/chatedit.rs
@@ -0,0 +1,1559 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2024 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+use async_lock::Mutex as AsyncMutex;
+use async_trait::async_trait;
+use atomic_float::AtomicF32;
+use darkfi::system::msleep;
+use miniquad::{window, KeyCode, KeyMods, MouseButton, TouchPhase};
+use rand::{rngs::OsRng, Rng};
+use std::{
+ collections::HashMap,
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc, Mutex as SyncMutex, OnceLock, Weak,
+ },
+ time::Instant,
+};
+
+use crate::{
+ error::Result,
+ gfx::{
+ GfxDrawCall, GfxDrawInstruction, GfxDrawMesh, GfxTextureId, GraphicsEventPublisherPtr,
+ Point, Rectangle, RenderApi, Vertex,
+ },
+ mesh::{MeshBuilder, MeshInfo, COLOR_BLUE, COLOR_WHITE},
+ prop::{
+ PropertyBool, PropertyColor, PropertyFloat32, PropertyPtr, PropertyRect, PropertyStr,
+ PropertyUint32, Role,
+ },
+ pubsub::Subscription,
+ scene::{Pimpl, SceneNodePtr, SceneNodeWeak},
+ text::{self, Glyph, GlyphPositionIter, TextShaperPtr},
+ util::{enumerate_ref, is_whitespace, zip3},
+ ExecutorPtr,
+};
+
+use super::{
+ editbox::{
+ editable::{Editable, Selection, TextIdx, TextPos},
+ eol_nudge,
+ repeat::{PressedKey, PressedKeysSmoothRepeat},
+ ALLOWED_KEYCODES, DISALLOWED_CHARS,
+ },
+ DrawUpdate, OnModify, UIObject,
+};
+
+#[derive(Clone)]
+struct TouchStartInfo {
+ pos: Point,
+ instant: std::time::Instant,
+}
+
+impl TouchStartInfo {
+ fn new(pos: Point) -> Self {
+ Self { pos, instant: std::time::Instant::now() }
+ }
+}
+
+#[derive(Clone)]
+enum TouchStateAction {
+ Inactive,
+ Started { pos: Point, instant: std::time::Instant },
+ StartSelect,
+ Select,
+ DragSelectHandle { side: isize },
+ ScrollHoriz { start_pos: Point, scroll_start: f32 },
+}
+
+struct TouchInfo {
+ state: TouchStateAction,
+ start: Option,
+
+ scroll: PropertyFloat32,
+}
+
+impl TouchInfo {
+ fn new(scroll: PropertyFloat32) -> Self {
+ Self { state: TouchStateAction::Inactive, start: None, scroll }
+ }
+
+ fn start(&mut self, pos: Point) {
+ debug!(target: "ui::chatedit", "TouchStateAction::Started");
+ self.state = TouchStateAction::Started { pos, instant: std::time::Instant::now() };
+ }
+
+ fn stop(&mut self) -> TouchStateAction {
+ std::mem::replace(&mut self.state, TouchStateAction::Inactive)
+ }
+
+ fn update(&mut self, pos: &Point) {
+ match &self.state {
+ TouchStateAction::Started { pos: start_pos, instant } => {
+ let travel_dist = pos.dist_sq(&start_pos);
+ let x_dist = pos.x - start_pos.x;
+ let elapsed = instant.elapsed().as_millis();
+
+ if travel_dist < 5. {
+ if elapsed > 1000 {
+ debug!(target: "ui::chatedit", "TouchStateAction::StartSelect");
+ self.state = TouchStateAction::StartSelect;
+ }
+ } else if x_dist.abs() > 5. {
+ debug!(target: "ui::chatedit", "TouchStateAction::ScrollHoriz");
+ let scroll_start = self.scroll.get();
+ self.state =
+ TouchStateAction::ScrollHoriz { start_pos: *start_pos, scroll_start };
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+pub type ChatEditPtr = Arc;
+
+pub struct ChatEdit {
+ node: SceneNodeWeak,
+ tasks: OnceLock>>,
+ render_api: RenderApi,
+ text_shaper: TextShaperPtr,
+ key_repeat: SyncMutex,
+
+ glyphs: SyncMutex>,
+ /// DC key for the text
+ text_dc_key: u64,
+ cursor_mesh: SyncMutex