2023-07-12 07:23:41 +00:00
|
|
|
pub mod api;
|
|
|
|
|
pub mod dialogue;
|
2023-08-22 07:03:29 +00:00
|
|
|
pub mod icontray;
|
2023-07-12 07:23:41 +00:00
|
|
|
pub mod ui;
|
|
|
|
|
|
|
|
|
|
use std::convert::TryInto;
|
2024-09-23 09:00:16 +00:00
|
|
|
use std::sync::{Arc, atomic::AtomicBool, atomic::Ordering};
|
2023-07-12 07:23:41 +00:00
|
|
|
use std::thread;
|
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
|
|
2024-01-14 11:12:20 +00:00
|
|
|
pub use api::*;
|
2025-02-21 09:46:11 +00:00
|
|
|
use blitstr2::GlyphStyle;
|
2024-01-14 11:12:20 +00:00
|
|
|
use gam::MenuItem;
|
|
|
|
|
use num_traits::FromPrimitive;
|
|
|
|
|
pub use ui::BUSY_ANIMATION_RATE_MS;
|
2024-09-23 09:00:16 +00:00
|
|
|
use ui::VisualProperties;
|
2025-02-21 09:46:11 +00:00
|
|
|
use ux_api::minigfx::*;
|
2024-09-23 09:00:16 +00:00
|
|
|
use xous::{CID, Error, SID, msg_scalar_unpack};
|
2023-07-12 07:23:41 +00:00
|
|
|
use xous_ipc::Buffer;
|
|
|
|
|
|
2023-11-30 15:05:41 +00:00
|
|
|
/// 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,
|
2024-01-14 11:12:20 +00:00
|
|
|
TextBounds::GrowableFromBl(Point::new(vp.margin.x, vp.layout_screensize.y), vp.bubble_width),
|
2023-11-30 15:05:41 +00:00
|
|
|
);
|
|
|
|
|
if hilite {
|
|
|
|
|
bubble_tv.border_width = 3;
|
|
|
|
|
} else {
|
|
|
|
|
bubble_tv.border_width = 1;
|
|
|
|
|
}
|
2024-01-14 11:12:20 +00:00
|
|
|
bubble_tv.clip_rect =
|
2025-02-21 09:46:11 +00:00
|
|
|
Some(Rectangle::new(Point::new(0, vp.status_height as isize + vp.margin.y), vp.layout_screensize));
|
2023-11-30 15:05:41 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-30 18:23:47 +00:00
|
|
|
/// 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,
|
2025-02-21 09:46:11 +00:00
|
|
|
anchor_y: isize,
|
2024-01-14 11:12:20 +00:00
|
|
|
) -> TextView {
|
2023-11-30 18:23:47 +00:00
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 07:23:41 +00:00
|
|
|
pub struct Chat {
|
|
|
|
|
cid: CID,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Chat {
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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
|
2024-01-14 11:12:20 +00:00
|
|
|
/// * `event_opcode` - to handle `ScalarMessage` representing a UI Event, such as F1 click, Left click,
|
|
|
|
|
/// Top Post, etc.
|
2023-08-31 19:41:40 +00:00
|
|
|
/// * `rawkeys_opcode` - to handle a raw-keystroke.
|
2023-07-12 07:23:41 +00:00
|
|
|
pub fn new(
|
2023-08-14 18:27:15 +00:00
|
|
|
app_name: &'static str,
|
|
|
|
|
app_menu: &'static str,
|
2023-07-12 07:23:41 +00:00
|
|
|
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();
|
|
|
|
|
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2023-07-12 07:23:41 +00:00
|
|
|
log::info!("Starting chat UI server",);
|
|
|
|
|
thread::spawn({
|
|
|
|
|
move || {
|
2023-08-14 18:27:15 +00:00
|
|
|
server(
|
|
|
|
|
chat_sid,
|
|
|
|
|
app_name,
|
|
|
|
|
app_menu,
|
|
|
|
|
app_cid,
|
|
|
|
|
opcode_post,
|
|
|
|
|
opcode_event,
|
|
|
|
|
opcode_rawkeys,
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
run_busy_animation,
|
|
|
|
|
busy_bumper_cid,
|
2023-08-14 18:27:15 +00:00
|
|
|
);
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Chat { cid: chat_cid }
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// Return the Chat App CID
|
|
|
|
|
///
|
|
|
|
|
/// This cid allows the Chat App to contact this Chat UI server
|
2024-01-14 11:12:20 +00:00
|
|
|
pub fn cid(&self) -> CID { self.cid }
|
2023-08-05 01:48:55 +00:00
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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
|
2023-08-10 10:22:15 +00:00
|
|
|
pub fn read_only(pddb_dict: &str, pddb_key: Option<&str>) -> Self {
|
|
|
|
|
let chat = Chat::new("_Chat Read_", "unused", None, None, None, None);
|
2023-07-12 07:23:41 +00:00
|
|
|
chat.dialogue_set(pddb_dict, pddb_key).unwrap();
|
|
|
|
|
chat
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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
|
2023-08-10 10:22:15 +00:00
|
|
|
pub fn dialogue_set(&self, pddb_dict: &str, pddb_key: Option<&str>) -> Result<(), Error> {
|
2024-09-22 07:31:14 +00:00
|
|
|
let dialogue =
|
|
|
|
|
api::Dialogue { dict: String::from(pddb_dict), key: pddb_key.map(|key| String::from(key)) };
|
2023-07-12 07:23:41 +00:00
|
|
|
match Buffer::into_buf(dialogue) {
|
|
|
|
|
Ok(buf) => buf.send(self.cid, ChatOp::DialogueSet as u32).map(|_| ()),
|
|
|
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-09 00:24:52 +00:00
|
|
|
/// Show some user help
|
|
|
|
|
pub fn help(&self) {
|
2024-01-14 11:12:20 +00:00
|
|
|
xous::send_message(self.cid, xous::Message::new_scalar(ChatOp::Help as usize, 0, 0, 0, 0))
|
|
|
|
|
.map(|_| ())
|
|
|
|
|
.expect("failed to get help");
|
2023-09-09 00:24:52 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// Add a new MenuItem to the App menu
|
|
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `item` - an item action not handled by the Chat UI
|
2023-08-14 18:27:15 +00:00
|
|
|
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),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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)
|
2023-07-12 07:23:41 +00:00
|
|
|
pub fn post_add(
|
|
|
|
|
&self,
|
|
|
|
|
author: &str,
|
2023-08-03 23:17:43 +00:00
|
|
|
timestamp: u64,
|
2023-07-12 07:23:41 +00:00
|
|
|
text: &str,
|
|
|
|
|
attach_url: Option<&str>,
|
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
|
let mut post = api::Post {
|
2024-09-22 06:17:03 +00:00
|
|
|
dialogue_id: String::new(),
|
|
|
|
|
author: String::new(),
|
2024-01-14 11:12:20 +00:00
|
|
|
timestamp,
|
2024-09-22 06:17:03 +00:00
|
|
|
text: String::new(),
|
2023-07-12 07:23:41 +00:00
|
|
|
attach_url: match attach_url {
|
2024-09-22 06:17:03 +00:00
|
|
|
Some(url) => Some(String::from(url)),
|
2023-07-12 07:23:41 +00:00
|
|
|
None => None,
|
|
|
|
|
},
|
|
|
|
|
};
|
2024-09-23 09:00:16 +00:00
|
|
|
post.author.push_str(author);
|
|
|
|
|
post.text.push_str(text);
|
2023-07-12 07:23:41 +00:00
|
|
|
match Buffer::into_buf(post) {
|
|
|
|
|
Ok(buf) => buf.send(self.cid, ChatOp::PostAdd as u32).map(|_| ()),
|
|
|
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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> {
|
2024-01-14 11:12:20 +00:00
|
|
|
xous::send_message(self.cid, xous::Message::new_scalar(ChatOp::PostDel as usize, index, 0, 0, 0))
|
|
|
|
|
.map(|_| ())
|
|
|
|
|
.expect("failed to delete Pose {index}");
|
2023-08-31 19:41:40 +00:00
|
|
|
Ok(())
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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
|
2023-08-03 23:17:43 +00:00
|
|
|
pub fn post_find(&self, author: &str, timestamp: u64) -> Result<Option<usize>, Error> {
|
2024-09-22 06:17:03 +00:00
|
|
|
let mut find = Find { author: String::new(), timestamp, key: None };
|
2024-09-23 09:00:16 +00:00
|
|
|
find.author.push_str(author);
|
2023-07-12 07:23:41 +00:00
|
|
|
match Buffer::into_buf(find) {
|
2023-08-17 19:11:43 +00:00
|
|
|
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),
|
|
|
|
|
},
|
2023-07-12 07:23:41 +00:00
|
|
|
Err(_) => Err(xous::Error::InternalError),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// Set various status flags on a Post in the current Dialogue
|
|
|
|
|
///
|
|
|
|
|
/// TODO: not implemented
|
2023-07-12 07:23:41 +00:00
|
|
|
pub fn post_flag(&self, _key: &str) -> Result<(), Error> {
|
|
|
|
|
log::warn!("not implemented");
|
|
|
|
|
Err(xous::Error::InternalError)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// Redraw our Chat UI.
|
2023-08-05 01:48:55 +00:00
|
|
|
pub fn redraw(&self) {
|
2024-01-14 11:12:20 +00:00
|
|
|
xous::send_message(self.cid, xous::Message::new_scalar(ChatOp::GamRedraw as usize, 0, 0, 0, 0))
|
|
|
|
|
.map(|_| ())
|
|
|
|
|
.expect("failed to Redraw Chat UI");
|
2023-08-05 01:48:55 +00:00
|
|
|
}
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
|
2023-10-28 17:08:38 +00:00
|
|
|
/// Set the status bar text.
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
2023-10-28 17:08:38 +00:00
|
|
|
/// `msg` - the text to show
|
|
|
|
|
///
|
|
|
|
|
/// This method implements the latest recommendation of panicing on internal errors.
|
|
|
|
|
pub fn set_status_text(&self, msg: &str) {
|
2024-09-22 06:17:03 +00:00
|
|
|
let bm = BusyMessage { busy_msg: String::from(msg) };
|
2024-01-14 11:12:20 +00:00
|
|
|
Buffer::into_buf(bm)
|
|
|
|
|
.expect("internal error")
|
|
|
|
|
.send(self.cid, ChatOp::SetStatusText as u32)
|
|
|
|
|
.expect("internal error");
|
2023-10-28 17:08:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_busy_state(&self, run: bool) {
|
|
|
|
|
xous::send_message(
|
|
|
|
|
self.cid,
|
2024-01-14 11:12:20 +00:00
|
|
|
xous::Message::new_scalar(
|
|
|
|
|
ChatOp::SetBusyAnimationState as usize,
|
2023-10-28 17:08:38 +00:00
|
|
|
if run { 1 } else { 0 },
|
2024-01-14 11:12:20 +00:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
),
|
2023-10-28 17:08:38 +00:00
|
|
|
)
|
|
|
|
|
.map(|_| ())
|
|
|
|
|
.expect("internal error");
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
}
|
2023-10-29 12:07:22 +00:00
|
|
|
|
|
|
|
|
/// 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) {
|
2024-09-22 06:17:03 +00:00
|
|
|
let bm = BusyMessage { busy_msg: String::from(msg) };
|
2024-01-14 11:12:20 +00:00
|
|
|
Buffer::into_buf(bm)
|
|
|
|
|
.expect("internal error")
|
|
|
|
|
.send(self.cid, ChatOp::SetStatusIdleText as u32)
|
|
|
|
|
.expect("internal error");
|
2023-10-29 12:07:22 +00:00
|
|
|
}
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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
|
2024-01-14 11:12:20 +00:00
|
|
|
/// * `run_busy_animation` - a shared `AtomicBool` which, when `true`, causes the loop to reschedule itself to
|
|
|
|
|
/// run.
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
pub fn busy_animator(
|
|
|
|
|
busy_bumper: SID,
|
2024-01-14 11:12:20 +00:00
|
|
|
busy_bumper_cid: CID,
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
chat_cid: CID,
|
2024-01-14 11:12:20 +00:00
|
|
|
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();
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
tt.sleep_ms(crate::BUSY_ANIMATION_RATE_MS).unwrap();
|
2024-01-14 11:12:20 +00:00
|
|
|
xous::try_send_message(
|
|
|
|
|
busy_bumper_cid,
|
|
|
|
|
xous::Message::new_scalar(BusyAnimOp::Pump as usize, 0, 0, 0, 0),
|
|
|
|
|
)
|
|
|
|
|
.ok();
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-01-14 11:12:20 +00:00
|
|
|
_ => {
|
|
|
|
|
log::warn!("Unexpected message: {:?}", msg);
|
|
|
|
|
}
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
}
|
2024-01-14 11:12:20 +00:00
|
|
|
}
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-31 19:41:40 +00:00
|
|
|
/// 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
|
2024-01-14 11:12:20 +00:00
|
|
|
/// * `event_opcode` - to handle `ScalarMessage` representing a UI Event, such as F1 click, Left click, Top
|
|
|
|
|
/// Post, etc.
|
2023-08-31 19:41:40 +00:00
|
|
|
/// * `rawkeys_opcode` - to handle a raw-keystroke.
|
2023-07-12 07:23:41 +00:00
|
|
|
pub fn server(
|
|
|
|
|
sid: SID,
|
2023-08-14 18:27:15 +00:00
|
|
|
app_name: &'static str,
|
|
|
|
|
app_menu: &'static str,
|
2023-07-12 07:23:41 +00:00
|
|
|
app_cid: Option<CID>,
|
|
|
|
|
opcode_post: Option<usize>,
|
|
|
|
|
opcode_event: Option<usize>,
|
|
|
|
|
opcode_rawkeys: Option<usize>,
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
run_busy_animation: Arc<AtomicBool>,
|
|
|
|
|
busy_bumper_cid: CID,
|
2023-07-12 07:23:41 +00:00
|
|
|
) -> ! {
|
|
|
|
|
//log_server::init_wait().unwrap();
|
|
|
|
|
log::set_max_level(log::LevelFilter::Info);
|
|
|
|
|
log::info!("my PID is {}", xous::process::id());
|
|
|
|
|
|
2023-08-14 18:27:15 +00:00
|
|
|
let mut ui = ui::Ui::new(sid, app_name, app_menu, app_cid, opcode_event);
|
2023-11-29 20:24:58 +00:00
|
|
|
let mut dialogue_key = None;
|
2023-07-12 07:23:41 +00:00
|
|
|
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()) {
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
Some(ChatOp::UpdateBusy) => {
|
|
|
|
|
ui.redraw_busy().expect("CHAT couldn't redraw");
|
|
|
|
|
}
|
2023-10-29 15:03:46 +00:00
|
|
|
Some(ChatOp::UpdateBusyForced) => {
|
|
|
|
|
ui.redraw_status_forced().expect("CHAT couldn't redraw");
|
|
|
|
|
}
|
2023-10-28 17:08:38 +00:00
|
|
|
Some(ChatOp::SetStatusText) => {
|
2024-01-14 11:12:20 +00:00
|
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
let s = buffer.to_original::<BusyMessage, _>().unwrap();
|
2024-09-22 06:17:03 +00:00
|
|
|
ui.set_status_text(s.busy_msg.as_str());
|
2023-10-28 17:08:38 +00:00
|
|
|
}
|
2023-10-29 12:07:22 +00:00
|
|
|
Some(ChatOp::SetStatusIdleText) => {
|
2024-01-14 11:12:20 +00:00
|
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
2023-10-29 12:07:22 +00:00
|
|
|
let s = buffer.to_original::<BusyMessage, _>().unwrap();
|
2024-09-22 06:17:03 +00:00
|
|
|
ui.set_status_idle_text(s.busy_msg.as_str());
|
2023-10-29 12:07:22 +00:00
|
|
|
}
|
2023-10-28 17:08:38 +00:00
|
|
|
Some(ChatOp::SetBusyAnimationState) => msg_scalar_unpack!(msg, state, _, _, _, {
|
|
|
|
|
if state != 0 {
|
|
|
|
|
ui.set_busy_state(true);
|
|
|
|
|
if !run_busy_animation.swap(true, Ordering::SeqCst) {
|
2024-01-14 11:12:20 +00:00
|
|
|
// 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();
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
}
|
2023-10-28 17:08:38 +00:00
|
|
|
} else {
|
2023-10-29 12:07:22 +00:00
|
|
|
run_busy_animation.store(false, Ordering::SeqCst);
|
2023-10-28 17:08:38 +00:00
|
|
|
ui.set_busy_state(false);
|
add busy-wait animation server and hooks for managing the same
Busy-wait animaitions are handled by a helper server that is normally
blocked. A single shared `bool` indicates whether the animation should
run or not.
When woken up, the busy-wait animator will check if the shared `bool`
is set, and if so, send a message to the main loop to update
the animation, then send wait an appropriate timeout and send itself
a message to re-check the animation status.
This particular format of server is fairly compact but can be vulnerable
to a "message storm" if busy state updates happen in rapid
succession without allowing the server to unblock and handle messages.
Thus, all messages to this server use a `try_send` method, with a silent
failure if the message can't be sent. In other words, if the animator
is already super busy and its queue is full, don't send it more messages,
just drop the existing updates so we can fail without a panic or deadlock.
The typical consequence of this is some UX inconsistency, which is
not a critical error.
2023-10-19 16:43:09 +00:00
|
|
|
}
|
2023-10-28 17:08:38 +00:00
|
|
|
}),
|
2023-08-29 18:33:36 +00:00
|
|
|
Some(ChatOp::DialogueSave) => {
|
2023-09-07 08:18:02 +00:00
|
|
|
log::info!("ChatOp::DialogueSave");
|
2023-08-29 18:33:36 +00:00
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 07:23:41 +00:00
|
|
|
Some(ChatOp::DialogueSet) => {
|
2023-08-10 10:22:15 +00:00
|
|
|
log::info!("ChatOp::DialogueSet");
|
2024-01-14 11:12:20 +00:00
|
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
2023-07-12 07:23:41 +00:00
|
|
|
let dialogue = buffer.to_original::<Dialogue, _>().unwrap();
|
2023-11-29 20:24:58 +00:00
|
|
|
dialogue_key = match dialogue.key {
|
2023-08-10 10:22:15 +00:00
|
|
|
Some(key) => Some(key.to_string()),
|
|
|
|
|
None => None,
|
|
|
|
|
};
|
2024-09-22 06:17:03 +00:00
|
|
|
ui.dialogue_set(dialogue.dict.as_str(), dialogue_key.as_deref());
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
Some(ChatOp::GamChangeFocus) => {
|
2023-08-17 19:11:43 +00:00
|
|
|
log::info!("ChatOp::GamChangeFocus");
|
2023-07-12 07:23:41 +00:00
|
|
|
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;
|
2023-08-17 19:11:43 +00:00
|
|
|
ui.event(Event::Focus);
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
Some(ChatOp::GamLine) => {
|
2023-08-12 02:09:35 +00:00
|
|
|
log::info!("got ChatOp::GamLine");
|
2024-01-14 11:12:20 +00:00
|
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
2024-09-22 06:17:03 +00:00
|
|
|
let s = buffer.as_flat::<String, _>().unwrap();
|
2023-08-22 07:03:29 +00:00
|
|
|
match s.as_str() {
|
2023-09-07 01:38:21 +00:00
|
|
|
"\u{0011}" => {}
|
|
|
|
|
"\u{0012}" => {}
|
|
|
|
|
"\u{0013}" => {}
|
|
|
|
|
"\u{0014}" => {}
|
|
|
|
|
"↑" => {}
|
|
|
|
|
"↓" => {}
|
|
|
|
|
"←" => {}
|
|
|
|
|
"→" => {}
|
2023-08-22 07:03:29 +00:00
|
|
|
_ => {
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-07 01:38:21 +00:00
|
|
|
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");
|
2023-09-07 08:18:02 +00:00
|
|
|
ui.set_menu_mode(true); // ← & → activate menus
|
2023-09-07 01:38:21 +00:00
|
|
|
ui.post_select(POST_SELECTED_PREV);
|
|
|
|
|
ui.redraw().expect("failed to redraw chat");
|
|
|
|
|
ui.event(Event::Up);
|
2023-09-07 08:18:02 +00:00
|
|
|
}
|
2023-09-07 01:38:21 +00:00
|
|
|
'↓' => {
|
|
|
|
|
log::info!("click ↓ : next post");
|
|
|
|
|
ui.post_select(POST_SELECTED_NEXT);
|
|
|
|
|
ui.redraw().expect("failed to redraw chat");
|
|
|
|
|
ui.event(Event::Down);
|
2023-09-07 08:18:02 +00:00
|
|
|
}
|
2023-09-07 01:38:21 +00:00
|
|
|
'←' => {
|
|
|
|
|
log::info!("click ← : raise app menu");
|
|
|
|
|
if ui.get_menu_mode() {
|
|
|
|
|
ui.raise_app_menu();
|
|
|
|
|
}
|
|
|
|
|
ui.event(Event::Left);
|
|
|
|
|
}
|
|
|
|
|
'→' => {
|
2023-09-07 08:18:02 +00:00
|
|
|
log::info!("click → : raise msg menu : pull request welcome!");
|
2023-09-07 01:38:21 +00:00
|
|
|
if ui.get_menu_mode() {
|
|
|
|
|
ui.raise_msg_menu();
|
|
|
|
|
}
|
|
|
|
|
ui.event(Event::Right);
|
|
|
|
|
}
|
|
|
|
|
_ => {
|
2023-09-07 08:18:02 +00:00
|
|
|
ui.set_menu_mode(false); // ← & → move input cursor
|
2023-09-07 01:38:21 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
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");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-09 00:24:52 +00:00
|
|
|
Some(ChatOp::Help) => {
|
|
|
|
|
log::info!("ChatOp::Help");
|
|
|
|
|
ui.help();
|
|
|
|
|
}
|
2023-07-12 07:23:41 +00:00
|
|
|
Some(ChatOp::GamRedraw) => {
|
2023-08-17 19:11:43 +00:00
|
|
|
log::info!("ChatOp::GamRedraw");
|
2023-07-12 07:23:41 +00:00
|
|
|
if allow_redraw {
|
|
|
|
|
ui.redraw().expect("CHAT couldn't redraw");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(ChatOp::PostAdd) => {
|
|
|
|
|
log::info!("ChatOp::PostAdd");
|
2023-11-29 20:24:58 +00:00
|
|
|
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,
|
2024-09-22 06:17:03 +00:00
|
|
|
post.author.as_str(),
|
2023-11-29 20:24:58 +00:00
|
|
|
post.timestamp,
|
2024-09-22 06:17:03 +00:00
|
|
|
post.text.as_str(),
|
2023-11-29 20:24:58 +00:00
|
|
|
None, // TODO implement
|
|
|
|
|
)
|
|
|
|
|
.unwrap(),
|
|
|
|
|
Err(e) => log::warn!("failed to deserialize Post: {:?}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => log::warn!("failed to PostAdd with Dialogue == None"),
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-07 08:18:02 +00:00
|
|
|
Some(ChatOp::PostDel) => {
|
|
|
|
|
xous::msg_scalar_unpack!(msg, index, _, _, _, {
|
|
|
|
|
log::info!("ChatOp::PostDel {index}");
|
|
|
|
|
ui.post_del(index).expect("failed to delete post {index}");
|
|
|
|
|
});
|
|
|
|
|
}
|
2023-07-12 07:23:41 +00:00
|
|
|
Some(ChatOp::PostFind) => {
|
|
|
|
|
log::info!("ChatOp::PostAdd");
|
2024-01-14 11:12:20 +00:00
|
|
|
let mut buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
2023-07-12 07:23:41 +00:00
|
|
|
if let Ok(mut find) = buffer.to_original::<Find, _>() {
|
2024-09-22 06:17:03 +00:00
|
|
|
find.key = ui.post_find(find.author.as_str(), find.timestamp);
|
2023-07-12 07:23:41 +00:00
|
|
|
buffer.replace(find).expect("couldn't serialize return");
|
|
|
|
|
} else {
|
|
|
|
|
log::warn!("failed to serialize Find");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Some(ChatOp::PostFlag) => {
|
|
|
|
|
log::warn!("ChatOp::PostFlag not implemented");
|
|
|
|
|
}
|
2023-08-14 18:27:15 +00:00
|
|
|
Some(ChatOp::MenuAdd) => {
|
|
|
|
|
log::warn!("ChatOp::MenuAdd not implemented");
|
2024-01-14 11:12:20 +00:00
|
|
|
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
|
2023-08-14 18:27:15 +00:00
|
|
|
if let Ok(menu_item) = buffer.to_original::<MenuItem, _>() {
|
|
|
|
|
ui.menu_add(menu_item);
|
|
|
|
|
} else {
|
|
|
|
|
log::warn!("failed to deserialize MenuItem");
|
|
|
|
|
}
|
2023-07-12 07:23:41 +00:00
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-14 11:12:20 +00:00
|
|
|
pub fn now() -> u64 { SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().try_into().unwrap() }
|
2023-10-29 12:07:22 +00:00
|
|
|
|
|
|
|
|
/// "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) {
|
2024-09-22 06:17:03 +00:00
|
|
|
let bm = BusyMessage { busy_msg: String::from(msg) };
|
2024-01-14 11:12:20 +00:00
|
|
|
Buffer::into_buf(bm)
|
|
|
|
|
.expect("internal error")
|
|
|
|
|
.send(chat_cid, ChatOp::SetStatusText as u32)
|
|
|
|
|
.expect("internal error");
|
2023-10-29 12:07:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn cf_set_busy_state(chat_cid: xous::CID, run: bool) {
|
|
|
|
|
xous::send_message(
|
|
|
|
|
chat_cid,
|
2024-01-14 11:12:20 +00:00
|
|
|
xous::Message::new_scalar(ChatOp::SetBusyAnimationState as usize, if run { 1 } else { 0 }, 0, 0, 0),
|
2023-10-29 12:07:22 +00:00
|
|
|
)
|
|
|
|
|
.map(|_| ())
|
|
|
|
|
.expect("internal error");
|
2024-01-14 11:12:20 +00:00
|
|
|
}
|