remove logic from status primitive and make the interface simpler

- status bar text is just status bar text now
- idle animation runs/stops based on an explicit run/stop message

Note that at the moment if one starts the busy bar and a modal pops
up, you might overflow the queue of the ui drawing thread with animation
requests, because the thread blocks when the modal is summoned.

Modals "take the screen" anyways, so maybe the solution to this is
to add an additional interlock around any modal calls within the UI
main thread. Need to think about it a bit more.
This commit is contained in:
bunnie 2023-10-29 01:08:38 +08:00
parent d844c4306c
commit 4e027b676c
6 changed files with 68 additions and 75 deletions

View file

@ -18,6 +18,10 @@
"ja": "WARNING: clock not set *EN*",
"zh": "WARNING: clock not set *EN*"
},
"mtxchat.status.default" : {
"en": "Use ← for login menu",
"en-tts": "Use ← for login menu"
},
"mtxchat.filter.failed": {
"en": "error: could not create filter",
"en-tts": "error: could not create filter",

View file

@ -83,6 +83,7 @@ pub struct MtxChat<'a> {
modals: Modals,
new_username: bool,
new_room: bool,
status: String,
}
impl<'a> MtxChat<'a> {
pub fn new(chat: &Chat) -> MtxChat {
@ -91,6 +92,8 @@ impl<'a> MtxChat<'a> {
let trng = Trng::new(&xns).unwrap();
let pddb = pddb::Pddb::new();
pddb.try_mount();
let status = t!("mtxchat.status.default", locales::LANG).to_owned();
chat.set_status_text(&status);
MtxChat {
chat: chat,
trng: trng,
@ -113,6 +116,7 @@ impl<'a> MtxChat<'a> {
modals: modals,
new_username: false,
new_room: false,
status,
}
}
@ -241,7 +245,6 @@ impl<'a> MtxChat<'a> {
pub fn connect(&mut self) -> bool {
log::info!("Attempting connect to Matrix server");
self.chat.set_busy_state(Some(t!("mtxchat.busy.connecting", locales::LANG).to_owned())).expect("couldn't set busy state");
if self.wifi() {
if self.login() {
if let Some(_room_id) = self.get_room_id() {
@ -253,7 +256,6 @@ impl<'a> MtxChat<'a> {
.show_notification(t!("mtxchat.listen.patience", locales::LANG), None)
.expect("notification failed");
}
self.chat.set_busy_state(None).expect("couldn't clear busy state");
return true;
} else {
self.modals
@ -275,11 +277,12 @@ impl<'a> MtxChat<'a> {
.expect("notification failed");
}
self.dialogue_set(None);
self.chat.set_busy_state(None).expect("couldn't clear busy state");
false
}
pub fn login(&mut self) -> bool {
self.chat.set_status_text(t!("mtxchat.busy.connecting", locales::LANG));
self.chat.set_busy_state(true);
self.token = self.get(TOKEN_KEY).unwrap_or(None);
self.logged_in = false;
@ -338,6 +341,8 @@ impl<'a> MtxChat<'a> {
self.unset(USER_DOMAIN_KEY)
.expect("failed to unset user domain");
}
self.chat.set_status_text(&self.status);
self.chat.set_busy_state(false);
self.logged_in
}
@ -629,7 +634,8 @@ impl<'a> MtxChat<'a> {
&self.room_id,
) {
(true, Some(token), Some(user_domain), Some(room_id)) => {
self.chat.set_busy_state(Some(t!("mtxchat.busy.sending", locales::LANG).to_owned())).expect("couldn't set busy state");
self.chat.set_status_text(t!("mtxchat.busy.sending", locales::LANG));
self.chat.set_busy_state(true);
log::info!("txn_id = {}", txn_id);
let mut url = Url::parse("https://matrix.org").unwrap();
url.set_host(Some(user_domain)).expect("failed to set host");
@ -638,7 +644,8 @@ impl<'a> MtxChat<'a> {
} else {
"FAILED TO SEND"
};
self.chat.set_busy_state(None).expect("couldn't clear busy state");
self.chat.set_status_text(&self.status);
self.chat.set_busy_state(false);
r
}
(false, _, _, _) => "Not logged in",

View file

@ -20,8 +20,8 @@
"en-tts": "no dialogues available to read"
},
"chat.status.initial" : {
"en": "use ← for login menu",
"en-tts": "use ← for login menu"
"en": "Idle",
"en-tts": "Idle"
},
"chat.help.navigation": {
"en": "use ↑ & ↓ to scroll thru old posts\nuse ← & → to show menus\nF1-F4 dont do anything yet.",

View file

@ -36,6 +36,8 @@ pub enum ChatOp {
/// Find a Post by timestamp and Author
PostFind,
PostFlag,
/// Set status bar text
SetStatusText,
/// Run or stop the busy animation.
SetBusyAnimationState,
/// Update just the state of the busy animation, if any. Internal opcode.
@ -133,8 +135,5 @@ pub enum AuthorFlag {
#[derive(Archive, Serialize, Deserialize, Debug)]
pub struct BusyMessage {
/// If None, restores the last non-busy status bar message, and stops the animation.
/// If Some, updates the status bar to contain the busy message and
/// starts the animation running.
pub busy_msg: Option<xous_ipc::String<128>>,
pub busy_msg: xous_ipc::String<128>,
}

View file

@ -12,7 +12,7 @@ use std::thread;
use std::time::{SystemTime, UNIX_EPOCH};
use std::sync::{Arc, atomic::AtomicBool, atomic::Ordering};
use xous::{Error, CID, SID};
use xous::{Error, CID, SID, msg_scalar_unpack};
use xous_ipc::Buffer;
pub struct Chat {
@ -239,32 +239,31 @@ impl Chat {
.expect("failed to Redraw Chat UI");
}
/// Run or stop the busy/waiting animation.
/// Set the status bar text.
///
/// # Arguments
///
/// `msg` - If `Some()`, updates the status bar to show the `msg` and start the animation running
/// If `None`, restores the last status message before being busy and stops the animation.
pub fn set_busy_state(&self, msg: Option<String>) -> Result<(), Error> {
/// `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: match msg {
None => None,
Some(m) => {
Some(
xous_ipc::String::from_str(&m)
)
}
}
busy_msg: xous_ipc::String::from_str(msg)
};
match Buffer::into_buf(bm) {
Ok(buf) => match buf.send(self.cid, ChatOp::SetBusyAnimationState as u32) {
Ok(..) => {
Ok(())
}
Err(_) => Err(xous::Error::InternalError),
},
Err(_) => Err(xous::Error::InternalError),
}
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");
}
}
@ -299,6 +298,7 @@ pub fn busy_animator(
ChatOp::UpdateBusy as usize, 0, 0, 0, 0)
).ok();
tt.sleep_ms(crate::BUSY_ANIMATION_RATE_MS).unwrap();
log::info!("busy!");
xous::try_send_message(busy_bumper_cid,
xous::Message::new_scalar(
BusyAnimOp::Pump as usize, 0, 0, 0, 0)
@ -346,37 +346,32 @@ pub fn server(
loop {
let msg = xous::receive_message(sid).unwrap();
log::debug!("got message {:?}", msg);
if ui.is_busy() {
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 {
// make sure the animation stops running. Not sure if this is the most efficient way to handle this,
// but I think an atomic bool set is just a couple dozen CPU cycles...?
run_busy_animation.store(false, Ordering::SeqCst);
}
match FromPrimitive::from_usize(msg.body.id()) {
Some(ChatOp::UpdateBusy) => {
ui.redraw_busy().expect("CHAT couldn't redraw");
}
Some(ChatOp::SetBusyAnimationState) => {
Some(ChatOp::SetStatusText) => {
let buffer = unsafe {
Buffer::from_memory_message(msg.body.memory_message().unwrap())
};
let s = buffer.to_original::<BusyMessage, _>().unwrap();
match s.busy_msg {
Some(msg) => {
ui.set_busy(msg.as_str().unwrap());
}
None => {
ui.clear_busy();
}
}
ui.set_status_text(s.busy_msg.as_str().unwrap());
}
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 {
ui.set_busy_state(false);
run_busy_animation.store(false, Ordering::SeqCst)
}
}),
Some(ChatOp::DialogueSave) => {
log::info!("ChatOp::DialogueSave");
ui.dialogue_save().expect("failed to save Dialogue");

View file

@ -64,8 +64,6 @@ pub(crate) struct Ui {
status_tv: TextView,
/// track the last time we update the status bar; use this avoid double-updating busy animations
status_last_update_ms: u64,
/// track the last non-busy message so we can restore it when the busy state stops
status_last_msg: Option<String>,
bubble_width: u16,
margin: Point, // margin to edge of canvas
bubble_margin: Point, // margin of text in bubbles
@ -163,7 +161,6 @@ impl Ui {
status_height,
status_tv,
status_last_update_ms,
status_last_msg: None,
post_selected: None,
post_anchor: None,
post_topdown: false,
@ -642,16 +639,8 @@ impl Ui {
self.status_tv.busy_animation_state.is_some()
}
/// If not currently busy, save the current status bar message and kick off the animation.
/// Updates the text of the status bar in all cases.
pub(crate) fn set_busy(&mut self, msg: &str) {
if self.status_tv.busy_animation_state.is_none() {
// stash a copy of the last message that wasn't a busy message
self.status_last_msg = Some(
self.status_tv.to_str().to_owned()
);
self.status_tv.busy_animation_state = Some(0);
}
/// Set the status bar text
pub(crate) fn set_status_text(&mut self, msg: &str) {
self.status_tv.clear_str();
write!(self.status_tv, "{}", msg).ok();
xous::send_message(self.app_cid.unwrap(),
@ -659,13 +648,12 @@ impl Ui {
ChatOp::UpdateBusy as usize, 0, 0, 0, 0)
).ok();
}
/// Stops the busy animation state
pub(crate) fn clear_busy(&mut self) {
self.status_tv.busy_animation_state = None;
if let Some(prev_msg) = self.status_last_msg.take() {
self.status_tv.clear_str();
write!(self.status_tv, "{}", prev_msg).ok();
/// Sets the status bar to animate the busy animation
pub(crate) fn set_busy_state(&mut self, run: bool) {
if run {
self.status_tv.busy_animation_state = Some(0); // the "glitch" to 0 is intentional, gives an indicator that a new op has started
} else {
self.status_tv.busy_animation_state = None;
}
}