mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-04 04:27:34 +00:00
663 lines
25 KiB
Rust
663 lines
25 KiB
Rust
pub mod api;
|
|
pub mod dialogue;
|
|
pub mod icontray;
|
|
pub mod ui;
|
|
|
|
use std::convert::TryInto;
|
|
use std::sync::{Arc, atomic::AtomicBool, atomic::Ordering};
|
|
use std::thread;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
pub use api::*;
|
|
use blitstr2::GlyphStyle;
|
|
use gam::MenuItem;
|
|
use num_traits::FromPrimitive;
|
|
pub use ui::BUSY_ANIMATION_RATE_MS;
|
|
use ui::VisualProperties;
|
|
use ux_api::minigfx::*;
|
|
use xous::{CID, Error, SID, msg_scalar_unpack};
|
|
use xous_ipc::Buffer;
|
|
|
|
/// Create a TextView with the default properties common to all text bubbles.
|
|
pub(crate) fn default_textview(
|
|
post: &crate::dialogue::post::Post,
|
|
hilite: bool,
|
|
vp: &VisualProperties,
|
|
) -> TextView {
|
|
use std::fmt::Write;
|
|
let mut bubble_tv = TextView::new(
|
|
vp.canvas,
|
|
TextBounds::GrowableFromBl(Point::new(vp.margin.x, vp.layout_screensize.y), vp.bubble_width),
|
|
);
|
|
if hilite {
|
|
bubble_tv.border_width = 3;
|
|
} else {
|
|
bubble_tv.border_width = 1;
|
|
}
|
|
bubble_tv.clip_rect =
|
|
Some(Rectangle::new(Point::new(0, vp.status_height as isize + vp.margin.y), vp.layout_screensize));
|
|
bubble_tv.draw_border = true;
|
|
bubble_tv.clear_area = true;
|
|
bubble_tv.rounded_border = Some(vp.bubble_radius);
|
|
bubble_tv.style = GlyphStyle::Regular;
|
|
bubble_tv.margin = vp.bubble_margin;
|
|
bubble_tv.ellipsis = false;
|
|
bubble_tv.insertion = None;
|
|
write!(bubble_tv.text, "{}", post.text()).expect("couldn't write history text to TextView");
|
|
bubble_tv
|
|
}
|
|
|
|
/// Return a TextView bubble representing a Dialogue Post
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `vp` - the visual properties to be applied to the textview
|
|
/// * `topdown` - direction of the layout
|
|
/// * `post` - the post to represent in a TextView bubble
|
|
/// * `dialogue` - containing the Post for context info
|
|
/// * `hilite` - hilite this Post on the screen (thicker border)
|
|
/// * `anchor_y` - the vertical position on screen to draw TextView bubble
|
|
fn bubble(
|
|
vp: &VisualProperties,
|
|
topdown: bool,
|
|
post: &crate::dialogue::post::Post,
|
|
dialogue: &crate::dialogue::Dialogue,
|
|
hilite: bool,
|
|
anchor_y: isize,
|
|
) -> TextView {
|
|
// create a textview with all of our default properties
|
|
let mut bubble_tv = default_textview(post, hilite, vp);
|
|
|
|
// set alignment of bubble left/right
|
|
let mut align_right = false;
|
|
let mut anchor_x = vp.margin.x; // default to align left
|
|
if let Some(author) = dialogue.author(post.author_id()) {
|
|
if author.flag_is(AuthorFlag::Right) {
|
|
// align right
|
|
align_right = true;
|
|
anchor_x = vp.layout_screensize.x - vp.margin.x;
|
|
}
|
|
}
|
|
// set the text bounds of the bubble and the growth direction
|
|
let anchor = Point::new(anchor_x, anchor_y);
|
|
let width = vp.bubble_width;
|
|
let text_bounds = match (topdown, align_right) {
|
|
(true, true) => TextBounds::GrowableFromTr(anchor, width),
|
|
(true, false) => TextBounds::GrowableFromTl(anchor, width),
|
|
(false, true) => TextBounds::GrowableFromBr(anchor, width),
|
|
(false, false) => TextBounds::GrowableFromBl(anchor, width),
|
|
};
|
|
|
|
bubble_tv.bounds_hint = text_bounds;
|
|
bubble_tv
|
|
}
|
|
|
|
pub struct Chat {
|
|
cid: CID,
|
|
}
|
|
|
|
impl Chat {
|
|
/// Create a new Chat UI
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `app_name` - registered with GAM
|
|
/// * `app_menu` - with menu items handled by the Chat App rather than the Chat UI
|
|
/// * `app_cid` - to accept messages from the Chat UI (see below)
|
|
/// * `post_opcode` - to handle a `MemoryMessage` containing a new outbound user Post
|
|
/// * `event_opcode` - to handle `ScalarMessage` representing a UI Event, such as F1 click, Left click,
|
|
/// Top Post, etc.
|
|
/// * `rawkeys_opcode` - to handle a raw-keystroke.
|
|
pub fn new(
|
|
app_name: &'static str,
|
|
app_menu: &'static str,
|
|
app_cid: Option<CID>,
|
|
opcode_post: Option<usize>,
|
|
opcode_event: Option<usize>,
|
|
opcode_rawkeys: Option<usize>,
|
|
) -> Self {
|
|
let chat_sid = xous::create_server().unwrap();
|
|
let chat_cid = xous::connect(chat_sid).unwrap();
|
|
|
|
let busy_bumper = xous::create_server().unwrap();
|
|
let busy_bumper_cid = xous::connect(busy_bumper).unwrap();
|
|
|
|
log::info!("starting idle animation runner");
|
|
let run_busy_animation = Arc::new(AtomicBool::new(false));
|
|
thread::spawn({
|
|
let run_busy_animation = run_busy_animation.clone();
|
|
move || {
|
|
busy_animator(busy_bumper, busy_bumper_cid, chat_cid, run_busy_animation);
|
|
}
|
|
});
|
|
|
|
log::info!("Starting chat UI server",);
|
|
thread::spawn({
|
|
move || {
|
|
server(
|
|
chat_sid,
|
|
app_name,
|
|
app_menu,
|
|
app_cid,
|
|
opcode_post,
|
|
opcode_event,
|
|
opcode_rawkeys,
|
|
run_busy_animation,
|
|
busy_bumper_cid,
|
|
);
|
|
}
|
|
});
|
|
|
|
Chat { cid: chat_cid }
|
|
}
|
|
|
|
/// Return the Chat App CID
|
|
///
|
|
/// This cid allows the Chat App to contact this Chat UI server
|
|
pub fn cid(&self) -> CID { self.cid }
|
|
|
|
/// Create an offline/read-only Chat UI over and existing Dialogue in pddb
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `pddb_dict` - the pddb dict holding all Dialogues for this Chat App
|
|
/// * `pddb_key` - the pddb key holding a Dialogue
|
|
pub fn read_only(pddb_dict: &str, pddb_key: Option<&str>) -> Self {
|
|
let chat = Chat::new("_Chat Read_", "unused", None, None, None, None);
|
|
chat.dialogue_set(pddb_dict, pddb_key).unwrap();
|
|
chat
|
|
}
|
|
|
|
/// Set the current Dialogue
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `pddb_dict` - the pddb dict holding all Dialogues for this Chat App
|
|
/// * `pddb_key` - the pddb key holding a Dialogue
|
|
pub fn dialogue_set(&self, pddb_dict: &str, pddb_key: Option<&str>) -> Result<(), Error> {
|
|
let dialogue =
|
|
api::Dialogue { dict: String::from(pddb_dict), key: pddb_key.map(|key| String::from(key)) };
|
|
match Buffer::into_buf(dialogue) {
|
|
Ok(buf) => buf.send(self.cid, ChatOp::DialogueSet as u32).map(|_| ()),
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
}
|
|
}
|
|
|
|
/// Show some user help
|
|
pub fn help(&self) {
|
|
xous::send_message(self.cid, xous::Message::new_scalar(ChatOp::Help as usize, 0, 0, 0, 0))
|
|
.map(|_| ())
|
|
.expect("failed to get help");
|
|
}
|
|
|
|
/// Add a new MenuItem to the App menu
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `item` - an item action not handled by the Chat UI
|
|
pub fn menu_add(&self, item: MenuItem) -> Result<(), Error> {
|
|
match Buffer::into_buf(item) {
|
|
Ok(buf) => buf.send(self.cid, ChatOp::MenuAdd as u32).map(|_| ()),
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
}
|
|
}
|
|
|
|
/// Add a new Post to the current Dialogue
|
|
///
|
|
/// note: posts are sorted by timestamp, so:
|
|
/// - `post_add` at beginning or end is fast (middle triggers a binary partition)
|
|
/// - if adding multiple posts then add oldest/newest last!
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `author` - the name of the Author of the Post
|
|
/// * `timestamp` - the timestamp of the Post
|
|
/// * `text` - the text content of the Post
|
|
/// * `attach_url` - a url of an attachment (image for example)
|
|
pub fn post_add(
|
|
&self,
|
|
author: &str,
|
|
timestamp: u64,
|
|
text: &str,
|
|
attach_url: Option<&str>,
|
|
) -> Result<(), Error> {
|
|
let mut post = api::Post {
|
|
dialogue_id: String::new(),
|
|
author: String::new(),
|
|
timestamp,
|
|
text: String::new(),
|
|
attach_url: match attach_url {
|
|
Some(url) => Some(String::from(url)),
|
|
None => None,
|
|
},
|
|
};
|
|
post.author.push_str(author);
|
|
post.text.push_str(text);
|
|
match Buffer::into_buf(post) {
|
|
Ok(buf) => buf.send(self.cid, ChatOp::PostAdd as u32).map(|_| ()),
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
}
|
|
}
|
|
|
|
/// Delete a Post from the current Dialogue
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `index` - the index of the Post to delete.
|
|
pub fn post_del(&self, index: usize) -> Result<(), Error> {
|
|
xous::send_message(self.cid, xous::Message::new_scalar(ChatOp::PostDel as usize, index, 0, 0, 0))
|
|
.map(|_| ())
|
|
.expect("failed to delete Pose {index}");
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns Some(index) of a matching Post by Author and Timestamp, or None
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `timestamp` - the Post timestamp criteria
|
|
/// * `author` - the Post Author criteria
|
|
///
|
|
/// Error if unable to send the msg to the Chat UI server
|
|
pub fn post_find(&self, author: &str, timestamp: u64) -> Result<Option<usize>, Error> {
|
|
let mut find = Find { author: String::new(), timestamp, key: None };
|
|
find.author.push_str(author);
|
|
match Buffer::into_buf(find) {
|
|
Ok(mut buf) => match buf.lend_mut(self.cid, ChatOp::PostFind as u32) {
|
|
Ok(..) => {
|
|
find = buf.to_original::<api::Find, _>().unwrap();
|
|
Ok(find.key)
|
|
}
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
},
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
}
|
|
}
|
|
|
|
/// Set various status flags on a Post in the current Dialogue
|
|
///
|
|
/// TODO: not implemented
|
|
pub fn post_flag(&self, _key: &str) -> Result<(), Error> {
|
|
log::warn!("not implemented");
|
|
Err(xous::Error::InternalError)
|
|
}
|
|
|
|
/// Redraw our Chat UI.
|
|
pub fn redraw(&self) {
|
|
xous::send_message(self.cid, xous::Message::new_scalar(ChatOp::GamRedraw as usize, 0, 0, 0, 0))
|
|
.map(|_| ())
|
|
.expect("failed to Redraw Chat UI");
|
|
}
|
|
|
|
/// Set the status bar text.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// `msg` - the text to show
|
|
///
|
|
/// This method implements the latest recommendation of panicing on internal errors.
|
|
pub fn set_status_text(&self, msg: &str) {
|
|
let bm = BusyMessage { busy_msg: String::from(msg) };
|
|
Buffer::into_buf(bm)
|
|
.expect("internal error")
|
|
.send(self.cid, ChatOp::SetStatusText as u32)
|
|
.expect("internal error");
|
|
}
|
|
|
|
pub fn set_busy_state(&self, run: bool) {
|
|
xous::send_message(
|
|
self.cid,
|
|
xous::Message::new_scalar(
|
|
ChatOp::SetBusyAnimationState as usize,
|
|
if run { 1 } else { 0 },
|
|
0,
|
|
0,
|
|
0,
|
|
),
|
|
)
|
|
.map(|_| ())
|
|
.expect("internal error");
|
|
}
|
|
|
|
/// Set status bar text when system is idle.
|
|
/// This is a convenience so we don't have to track the ins/outs of busy/idle state
|
|
/// and update the text, especially when we have multiple potential servers vying
|
|
/// to set a busy state.
|
|
pub fn set_status_idle_text(&self, msg: &str) {
|
|
let bm = BusyMessage { busy_msg: String::from(msg) };
|
|
Buffer::into_buf(bm)
|
|
.expect("internal error")
|
|
.send(self.cid, ChatOp::SetStatusIdleText as u32)
|
|
.expect("internal error");
|
|
}
|
|
}
|
|
|
|
/// Helper server that pumps the busy animation state until instructed to stop.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `busy_bumper` - the server ID to use for the helper server
|
|
/// * `busy_bumper_cid` - the corresponding connection ID
|
|
/// * `chat_cid` - the CID to the main chat loop, used to initiate redraw events as necessary
|
|
/// * `run_busy_animation` - a shared `AtomicBool` which, when `true`, causes the loop to reschedule itself to
|
|
/// run.
|
|
pub fn busy_animator(
|
|
busy_bumper: SID,
|
|
busy_bumper_cid: CID,
|
|
chat_cid: CID,
|
|
run_busy_animation: Arc<AtomicBool>,
|
|
) {
|
|
let tt = ticktimer_server::Ticktimer::new().unwrap();
|
|
loop {
|
|
let msg = xous::receive_message(busy_bumper).unwrap();
|
|
match FromPrimitive::from_usize(msg.body.id()) {
|
|
Some(BusyAnimOp::Start) => {
|
|
tt.sleep_ms(crate::BUSY_ANIMATION_RATE_MS).unwrap();
|
|
xous::try_send_message(
|
|
busy_bumper_cid,
|
|
xous::Message::new_scalar(BusyAnimOp::Pump as usize, 0, 0, 0, 0),
|
|
)
|
|
.ok();
|
|
}
|
|
Some(BusyAnimOp::Pump) => {
|
|
if run_busy_animation.load(Ordering::SeqCst) {
|
|
xous::try_send_message(
|
|
chat_cid,
|
|
xous::Message::new_scalar(ChatOp::UpdateBusy as usize, 0, 0, 0, 0),
|
|
)
|
|
.ok();
|
|
tt.sleep_ms(crate::BUSY_ANIMATION_RATE_MS).unwrap();
|
|
xous::try_send_message(
|
|
busy_bumper_cid,
|
|
xous::Message::new_scalar(BusyAnimOp::Pump as usize, 0, 0, 0, 0),
|
|
)
|
|
.ok();
|
|
}
|
|
}
|
|
_ => {
|
|
log::warn!("Unexpected message: {:?}", msg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The Chat UI server a manages a Chat UI to read a display and navigate a
|
|
/// series of Posts in a Dialogue stored in the pddb - and to Author a new
|
|
/// Post.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `app_name` - registered with GAM
|
|
/// * `app_menu` - with menu items handled by the Chat App rather than the Chat UI
|
|
/// * `app_cid` - to accept messages from the Chat UI (see below)
|
|
/// * `post_opcode` - to handle a `MemoryMessage` containing a new outbound user Post
|
|
/// * `event_opcode` - to handle `ScalarMessage` representing a UI Event, such as F1 click, Left click, Top
|
|
/// Post, etc.
|
|
/// * `rawkeys_opcode` - to handle a raw-keystroke.
|
|
pub fn server(
|
|
sid: SID,
|
|
app_name: &'static str,
|
|
app_menu: &'static str,
|
|
app_cid: Option<CID>,
|
|
opcode_post: Option<usize>,
|
|
opcode_event: Option<usize>,
|
|
opcode_rawkeys: Option<usize>,
|
|
run_busy_animation: Arc<AtomicBool>,
|
|
busy_bumper_cid: CID,
|
|
) -> ! {
|
|
//log_server::init_wait().unwrap();
|
|
log::set_max_level(log::LevelFilter::Info);
|
|
log::info!("my PID is {}", xous::process::id());
|
|
|
|
let mut ui = ui::Ui::new(sid, app_name, app_menu, app_cid, opcode_event);
|
|
let mut dialogue_key = None;
|
|
let mut allow_redraw = false;
|
|
loop {
|
|
let msg = xous::receive_message(sid).unwrap();
|
|
log::debug!("got message {:?}", msg);
|
|
match FromPrimitive::from_usize(msg.body.id()) {
|
|
Some(ChatOp::UpdateBusy) => {
|
|
ui.redraw_busy().expect("CHAT couldn't redraw");
|
|
}
|
|
Some(ChatOp::UpdateBusyForced) => {
|
|
ui.redraw_status_forced().expect("CHAT couldn't redraw");
|
|
}
|
|
Some(ChatOp::SetStatusText) => {
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
let s = buffer.to_original::<BusyMessage, _>().unwrap();
|
|
ui.set_status_text(s.busy_msg.as_str());
|
|
}
|
|
Some(ChatOp::SetStatusIdleText) => {
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
let s = buffer.to_original::<BusyMessage, _>().unwrap();
|
|
ui.set_status_idle_text(s.busy_msg.as_str());
|
|
}
|
|
Some(ChatOp::SetBusyAnimationState) => msg_scalar_unpack!(msg, state, _, _, _, {
|
|
if state != 0 {
|
|
ui.set_busy_state(true);
|
|
if !run_busy_animation.swap(true, Ordering::SeqCst) {
|
|
// only send off the Pump request on the transition from false->true; this causes the
|
|
// machine to run
|
|
xous::try_send_message(
|
|
busy_bumper_cid,
|
|
xous::Message::new_scalar(BusyAnimOp::Pump as usize, 0, 0, 0, 0),
|
|
)
|
|
.ok();
|
|
}
|
|
} else {
|
|
run_busy_animation.store(false, Ordering::SeqCst);
|
|
ui.set_busy_state(false);
|
|
}
|
|
}),
|
|
Some(ChatOp::DialogueSave) => {
|
|
log::info!("ChatOp::DialogueSave");
|
|
ui.dialogue_save().expect("failed to save Dialogue");
|
|
ui.dialogue_read().expect("failed to read Dialogue");
|
|
if allow_redraw {
|
|
ui.redraw().expect("CHAT couldn't redraw");
|
|
}
|
|
}
|
|
Some(ChatOp::DialogueSet) => {
|
|
log::info!("ChatOp::DialogueSet");
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
let dialogue = buffer.to_original::<Dialogue, _>().unwrap();
|
|
dialogue_key = match dialogue.key {
|
|
Some(key) => Some(key.to_string()),
|
|
None => None,
|
|
};
|
|
ui.dialogue_set(dialogue.dict.as_str(), dialogue_key.as_deref());
|
|
}
|
|
Some(ChatOp::GamChangeFocus) => {
|
|
log::info!("ChatOp::GamChangeFocus");
|
|
xous::msg_scalar_unpack!(msg, new_state_code, _, _, _, {
|
|
let new_state = gam::FocusState::convert_focus_change(new_state_code);
|
|
match new_state {
|
|
gam::FocusState::Background => {
|
|
allow_redraw = false;
|
|
}
|
|
gam::FocusState::Foreground => {
|
|
allow_redraw = true;
|
|
ui.event(Event::Focus);
|
|
}
|
|
}
|
|
})
|
|
}
|
|
Some(ChatOp::GamLine) => {
|
|
log::info!("got ChatOp::GamLine");
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
let s = buffer.as_flat::<String, _>().unwrap();
|
|
match s.as_str() {
|
|
"\u{0011}" => {}
|
|
"\u{0012}" => {}
|
|
"\u{0013}" => {}
|
|
"\u{0014}" => {}
|
|
"↑" => {}
|
|
"↓" => {}
|
|
"←" => {}
|
|
"→" => {}
|
|
_ => {
|
|
drop(buffer);
|
|
if let Some(cid) = app_cid {
|
|
if let Some(opcode) = opcode_post {
|
|
log::info!("Forwarding msg to Chat App: {:?}", msg);
|
|
msg.forward(cid, opcode).expect("failed to fwd msg");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(ChatOp::GamRawkeys) => {
|
|
log::info!("got ChatOp::GamRawkeys");
|
|
xous::msg_scalar_unpack!(msg, k1, k2, k3, k4, {
|
|
log::info!("got Chat UI RawKey :{}:{}:{}:{}:", k1, k2, k3, k4);
|
|
match core::char::from_u32(k1 as u32).unwrap_or('\u{0000}') {
|
|
F1 => {
|
|
log::info!("click F1 : pull request welcome!");
|
|
ui.event(Event::F1);
|
|
}
|
|
F2 => {
|
|
log::info!("click F2 : pull request welcome!");
|
|
ui.event(Event::F2);
|
|
}
|
|
F3 => {
|
|
log::info!("click F3 : pull request welcome!");
|
|
ui.event(Event::F3);
|
|
}
|
|
F4 => {
|
|
log::info!("click F4 : pull request welcome!");
|
|
ui.event(Event::F4);
|
|
}
|
|
'↑' => {
|
|
log::info!("click ↑ : previous post");
|
|
ui.set_menu_mode(true); // ← & → activate menus
|
|
ui.post_select(POST_SELECTED_PREV);
|
|
ui.redraw().expect("failed to redraw chat");
|
|
ui.event(Event::Up);
|
|
}
|
|
'↓' => {
|
|
log::info!("click ↓ : next post");
|
|
ui.post_select(POST_SELECTED_NEXT);
|
|
ui.redraw().expect("failed to redraw chat");
|
|
ui.event(Event::Down);
|
|
}
|
|
'←' => {
|
|
log::info!("click ← : raise app menu");
|
|
if ui.get_menu_mode() {
|
|
ui.raise_app_menu();
|
|
}
|
|
ui.event(Event::Left);
|
|
}
|
|
'→' => {
|
|
log::info!("click → : raise msg menu : pull request welcome!");
|
|
if ui.get_menu_mode() {
|
|
ui.raise_msg_menu();
|
|
}
|
|
ui.event(Event::Right);
|
|
}
|
|
_ => {
|
|
ui.set_menu_mode(false); // ← & → move input cursor
|
|
}
|
|
}
|
|
});
|
|
if let Some(cid) = app_cid {
|
|
if let Some(opcode) = opcode_rawkeys {
|
|
log::info!("Forwarding msg to Chat App: {:?}", msg);
|
|
msg.forward(cid, opcode).expect("failed to fwd rawkey");
|
|
}
|
|
}
|
|
}
|
|
Some(ChatOp::Help) => {
|
|
log::info!("ChatOp::Help");
|
|
ui.help();
|
|
}
|
|
Some(ChatOp::GamRedraw) => {
|
|
log::info!("ChatOp::GamRedraw");
|
|
if allow_redraw {
|
|
ui.redraw().expect("CHAT couldn't redraw");
|
|
}
|
|
}
|
|
Some(ChatOp::PostAdd) => {
|
|
log::info!("ChatOp::PostAdd");
|
|
match dialogue_key {
|
|
Some(ref dialogue_id) => {
|
|
let buffer =
|
|
unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
match buffer.to_original::<api::Post, _>() {
|
|
Ok(post) => ui
|
|
.post_add(
|
|
&dialogue_id,
|
|
post.author.as_str(),
|
|
post.timestamp,
|
|
post.text.as_str(),
|
|
None, // TODO implement
|
|
)
|
|
.unwrap(),
|
|
Err(e) => log::warn!("failed to deserialize Post: {:?}", e),
|
|
}
|
|
}
|
|
None => log::warn!("failed to PostAdd with Dialogue == None"),
|
|
}
|
|
}
|
|
Some(ChatOp::PostDel) => {
|
|
xous::msg_scalar_unpack!(msg, index, _, _, _, {
|
|
log::info!("ChatOp::PostDel {index}");
|
|
ui.post_del(index).expect("failed to delete post {index}");
|
|
});
|
|
}
|
|
Some(ChatOp::PostFind) => {
|
|
log::info!("ChatOp::PostAdd");
|
|
let mut buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
if let Ok(mut find) = buffer.to_original::<Find, _>() {
|
|
find.key = ui.post_find(find.author.as_str(), find.timestamp);
|
|
buffer.replace(find).expect("couldn't serialize return");
|
|
} else {
|
|
log::warn!("failed to serialize Find");
|
|
}
|
|
}
|
|
Some(ChatOp::PostFlag) => {
|
|
log::warn!("ChatOp::PostFlag not implemented");
|
|
}
|
|
Some(ChatOp::MenuAdd) => {
|
|
log::warn!("ChatOp::MenuAdd not implemented");
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
|
if let Ok(menu_item) = buffer.to_original::<MenuItem, _>() {
|
|
ui.menu_add(menu_item);
|
|
} else {
|
|
log::warn!("failed to deserialize MenuItem");
|
|
}
|
|
}
|
|
Some(ChatOp::Quit) => {
|
|
log::error!("got Quit");
|
|
break;
|
|
}
|
|
_ => log::warn!("got unknown message"),
|
|
}
|
|
log::trace!("reached bottom of main loop");
|
|
}
|
|
// clean up our program
|
|
log::error!("main loop exit, destroying servers");
|
|
xous::destroy_server(sid).unwrap();
|
|
log::trace!("quitting");
|
|
xous::terminate_process(0)
|
|
}
|
|
|
|
pub fn now() -> u64 { SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().try_into().unwrap() }
|
|
|
|
/// "context-free" (cf) communication with the chat object.
|
|
/// This is accomplished by making a copy of the connection to the chat server.
|
|
pub fn cf_set_status_text(chat_cid: xous::CID, msg: &str) {
|
|
let bm = BusyMessage { busy_msg: String::from(msg) };
|
|
Buffer::into_buf(bm)
|
|
.expect("internal error")
|
|
.send(chat_cid, ChatOp::SetStatusText as u32)
|
|
.expect("internal error");
|
|
}
|
|
|
|
pub fn cf_set_busy_state(chat_cid: xous::CID, run: bool) {
|
|
xous::send_message(
|
|
chat_cid,
|
|
xous::Message::new_scalar(ChatOp::SetBusyAnimationState as usize, if run { 1 } else { 0 }, 0, 0, 0),
|
|
)
|
|
.map(|_| ())
|
|
.expect("internal error");
|
|
}
|