mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-10 06:36:32 +00:00
We can't support this anyways, because the textviews are immediately drawn on call by the calling app
244 lines
9.4 KiB
Rust
244 lines
9.4 KiB
Rust
#![cfg_attr(target_os = "none", no_std)]
|
|
#![cfg_attr(target_os = "none", no_main)]
|
|
|
|
use log::{error, info};
|
|
|
|
use core::fmt::Write;
|
|
use core::convert::TryFrom;
|
|
|
|
use rkyv::Unarchive;
|
|
use rkyv::archived_value;
|
|
use core::pin::Pin;
|
|
|
|
use ime_plugin_api::{ImeFrontEndApi, ImeFrontEnd};
|
|
use graphics_server::{Gid, Point, Rectangle, TextBounds, TextView, DrawStyle, GlyphStyle, PixelColor};
|
|
|
|
use heapless::spsc::Queue;
|
|
use heapless::consts::U16;
|
|
|
|
mod cmds;
|
|
use cmds::*;
|
|
|
|
#[derive(Debug)]
|
|
struct History {
|
|
// the history record
|
|
pub text: xous::String<1024>,
|
|
// if true, this was input from the user; if false, it's a response from the shell
|
|
pub is_input: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Repl {
|
|
// optional structures that indicate new input to the Repl loop per iteration
|
|
// an input string
|
|
input: Option<xous::String<1024>>,
|
|
// examples of future inputs: returned values via other services
|
|
|
|
// record our input history
|
|
history: Queue<History, U16>,
|
|
content: Gid,
|
|
gam: xous::CID,
|
|
|
|
// variables that define our graphical attributes
|
|
screensize: Point,
|
|
bubble_width: u16,
|
|
margin: Point, // margin to edge of canvas
|
|
bubble_margin: Point, // margin of text in bubbles
|
|
bubble_radius: u16,
|
|
bubble_space: i16, // spacing between text bubbles
|
|
|
|
// command environment
|
|
env: CmdEnv,
|
|
}
|
|
impl Repl{
|
|
fn new(my_server_name: &str) -> Self {
|
|
let gam_conn = xous_names::request_connection_blocking(xous::names::SERVER_NAME_GAM).expect("SHCH: can't connect to GAM");
|
|
let content = gam::request_content_canvas(gam_conn, my_server_name).expect("SHCH: couldn't get content canvas");
|
|
let screensize = gam::get_canvas_bounds(gam_conn, content).expect("SHCH: couldn't get dimensions of content canvas");
|
|
Repl {
|
|
input: None,
|
|
history: Queue::new(),
|
|
content,
|
|
gam: gam_conn,
|
|
screensize,
|
|
bubble_width: ((screensize.x / 5) * 4) as u16, // 80% width for the text bubbles
|
|
margin: Point::new(4, 4),
|
|
bubble_margin: Point::new(4, 4),
|
|
bubble_radius: 4,
|
|
bubble_space: 4,
|
|
env: CmdEnv::new(gam_conn),
|
|
}
|
|
}
|
|
|
|
/// accept a new input string
|
|
fn input(&mut self, line: &str) -> Result<(), xous::Error> {
|
|
let mut local = xous::String::<1024>::new();
|
|
write!(local, "{}", line).expect("SHCH: line too long for history buffer");
|
|
|
|
self.input = Some(local);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn circular_push(&mut self, item: History) {
|
|
if self.history.len() == self.history.capacity() {
|
|
self.history.dequeue().expect("SHCH: couldn't dequeue historye");
|
|
}
|
|
self.history.enqueue(item).expect("SHCH: couldn't store input line");
|
|
}
|
|
|
|
/// update the loop, in response to various inputs
|
|
fn update(&mut self) -> Result<(), xous::Error> {
|
|
let debug1 = false;
|
|
// if we had an input string, do something
|
|
if let Some(local) = self.input {
|
|
let input_history = History {
|
|
text: local,
|
|
is_input: true,
|
|
};
|
|
self.circular_push(input_history);
|
|
}
|
|
|
|
// AT THIS POINT: if we have other inputs, update accordingly
|
|
// other inputs might be, for example, events that came in from other servers that would
|
|
// side effect our commands
|
|
|
|
// redraw UI once upon accepting all input
|
|
self.redraw().expect("SHCH: can't redraw");
|
|
|
|
// take the input and pass it on to the various command parsers, and attach result
|
|
if let Some(mut local) = self.input {
|
|
if let Some(res) = self.env.dispatch(&mut local).expect("SHCH: command dispatch failed") {
|
|
let mut response = xous::String::<1024>::new();
|
|
write!(response, "{}", res).expect("SHCH: can't copy result to history");
|
|
let output_history = History {
|
|
text: response,
|
|
is_input: false
|
|
};
|
|
self.circular_push(output_history);
|
|
}
|
|
}
|
|
|
|
// clear all the inputs to the loop, so we don't process them twice
|
|
self.input = None;
|
|
// redraw UI now that we've responded
|
|
self.redraw().expect("SHCH: can't redraw");
|
|
|
|
if debug1 {
|
|
for h in self.history.iter() {
|
|
info!("SHCH: command history is_input: {}, text:{}", h.is_input, h.text);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn clear_area(&self) {
|
|
gam::draw_rectangle(self.gam, self.content,
|
|
Rectangle::new_with_style(Point::new(0, 0), self.screensize,
|
|
DrawStyle {
|
|
fill_color: Some(PixelColor::Light),
|
|
stroke_color: None,
|
|
stroke_width: 0
|
|
}
|
|
)).expect("SHCH: can't clear content area");
|
|
}
|
|
fn redraw(&mut self) -> Result<(), xous::Error> {
|
|
self.clear_area();
|
|
|
|
// this defines the bottom border of the text bubbles as they stack up wards
|
|
let mut bubble_baseline = self.screensize.y - self.margin.y;
|
|
|
|
// iterator returns from oldest to newest
|
|
// .rev() iterator is from newest to oldest
|
|
for h in self.history.iter().rev() {
|
|
let mut bubble_tv =
|
|
if h.is_input {
|
|
TextView::new(self.content,
|
|
TextBounds::GrowableFromBr(
|
|
Point::new(self.screensize.x - self.margin.x, bubble_baseline),
|
|
self.bubble_width))
|
|
} else {
|
|
TextView::new(self.content,
|
|
TextBounds::GrowableFromBl(
|
|
Point::new(self.margin.x, bubble_baseline),
|
|
self.bubble_width))
|
|
};
|
|
if h.is_input {
|
|
bubble_tv.border_width = 1;
|
|
} else {
|
|
bubble_tv.border_width = 2;
|
|
}
|
|
bubble_tv.draw_border = true;
|
|
bubble_tv.clear_area = true;
|
|
bubble_tv.rounded_border = Some(self.bubble_radius);
|
|
bubble_tv.style = GlyphStyle::Small;
|
|
bubble_tv.margin = self.bubble_margin;
|
|
bubble_tv.ellipsis = false; bubble_tv.insertion = None;
|
|
write!(bubble_tv.text, "{}", h.text.as_str().expect("SHCH: couldn't convert history text")).expect("SHCH: couldn't write history text to TextView");
|
|
gam::post_textview(self.gam, &mut bubble_tv).expect("SHCH: couldn't render bubble textview");
|
|
|
|
if let Some(bounds) = bubble_tv.bounds_computed {
|
|
// we only subtract 1x of the margin because the bounds were computed from a "bottom right" that already counted
|
|
// the margin once.
|
|
bubble_baseline -= (bounds.br.y - bounds.tl.y) + self.bubble_space + self.bubble_margin.y;
|
|
if bubble_baseline <= 0 {
|
|
// don't draw history that overflows the top of the screen
|
|
break;
|
|
}
|
|
} else {
|
|
break; // we get None on the bounds computed if the text view fell off the top of the screen
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[xous::xous_main]
|
|
fn xmain() -> ! {
|
|
let debug1 = false;
|
|
log_server::init_wait().unwrap();
|
|
info!("SHCH: my PID is {}", xous::process::id());
|
|
|
|
let shch_sid = xous_names::register_name(xous::names::SERVER_NAME_SHELLCHAT).expect("SHCH: can't register server");
|
|
info!("SHCH: registered with NS -- {:?}", shch_sid);
|
|
|
|
let imef_conn = xous_names::request_connection_blocking(xous::names::SERVER_NAME_IME_FRONT).expect("SHCH: can't connect to IMEF");
|
|
let imef = ImeFrontEnd { connection: Some(imef_conn) };
|
|
imef.register_listener(xous::names::SERVER_NAME_SHELLCHAT).expect("SHCH: couldn't request events from IMEF");
|
|
|
|
let mut repl = Repl::new(xous::names::SERVER_NAME_SHELLCHAT);
|
|
let mut update_repl = false;
|
|
info!("SHCH: starting main loop");
|
|
loop {
|
|
let envelope = xous::receive_message(shch_sid).unwrap();
|
|
if debug1{info!("SHCH: got message {:?}", envelope);}
|
|
if let xous::Message::Borrow(m) = &envelope.body {
|
|
let buf = unsafe { xous::XousBuffer::from_memory_message(m) };
|
|
let bytes = Pin::new(buf.as_ref());
|
|
let value = unsafe {
|
|
archived_value::<ime_plugin_api::ImefOpcode>(&bytes, m.id as usize)
|
|
};
|
|
match &*value {
|
|
rkyv::Archived::<ime_plugin_api::ImefOpcode>::GotInputLine(rkyv_s) => {
|
|
let s: xous::String<4000> = rkyv_s.unarchive();
|
|
repl.input(s.as_str().expect("SHCH: couldn't convert incoming string")).expect("SHCH: REPL couldn't accept input string");
|
|
update_repl = true; // set a flag, instead of calling here, so message can drop and calling server is released
|
|
},
|
|
_ => panic!("SHCH: invalid response from server -- corruption occurred in MemoryMessage")
|
|
};
|
|
} else if let Ok(opcode) = content_plugin_api::Opcode::try_from(&envelope.body) {
|
|
match opcode {
|
|
content_plugin_api::Opcode::Redraw => {
|
|
repl.redraw().expect("SHCH: REPL couldn't redraw");
|
|
}
|
|
}
|
|
} else {
|
|
error!("SHCH: couldn't convert message");
|
|
}
|
|
|
|
if update_repl {
|
|
repl.update().expect("SHCH: REPL had problems updating");
|
|
update_repl = false;
|
|
}
|
|
}
|
|
}
|