mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
add a progress bar/slider element. getting there.
This commit is contained in:
parent
1d3eaa4f9a
commit
cfd5e18549
3 changed files with 273 additions and 26 deletions
|
|
@ -47,6 +47,7 @@ General form for modals:
|
|||
- slider - left/right moves the slider, enter/select closes the modal
|
||||
*/
|
||||
use enum_dispatch::enum_dispatch;
|
||||
use log_server::init;
|
||||
use xous::MessageEnvelope;
|
||||
use xous::send_message;
|
||||
|
||||
|
|
@ -788,9 +789,7 @@ impl ActionApi for Notification {
|
|||
} else {
|
||||
modal.canvas_width - modal.margin * 2
|
||||
};
|
||||
log::info!("tw: {}", textwidth);
|
||||
let offset = (modal.canvas_width - textwidth) / 2;
|
||||
log::info!("offset2: {}", offset);
|
||||
tv.bounds_computed = None;
|
||||
tv.bounds_hint = TextBounds::BoundingBox(Rectangle::new(
|
||||
Point::new(offset, at_height + modal.margin * 2),
|
||||
|
|
@ -834,23 +833,197 @@ pub struct Slider {
|
|||
pub action_conn: xous::CID,
|
||||
pub action_opcode: u32,
|
||||
pub action_payload: u32,
|
||||
pub is_progressbar: bool,
|
||||
pub is_password: bool,
|
||||
pub show_legend: bool,
|
||||
pub units: xous_ipc::String::<8>,
|
||||
}
|
||||
impl Slider {
|
||||
pub fn new(action_conn: xous::CID, action_opcode: u32, min: u32, max: u32, step: u32, units: Option<&str>, initial_setting: u32, is_progressbar: bool, show_legend: bool) -> Self {
|
||||
let checked_units = if let Some(unit_str) = units {
|
||||
if unit_str.len() < 8 {
|
||||
String::<8>::from_str(unit_str)
|
||||
} else {
|
||||
log::error!("Unit string must be less than 8 *bytes* long (are you using unicode?), ignoring length {} string", unit_str.len());
|
||||
String::<8>::new()
|
||||
}
|
||||
} else {
|
||||
String::<8>::new() // just populate with a blank string, easier than checking Some/None later on everywhere
|
||||
};
|
||||
|
||||
Slider {
|
||||
action_conn,
|
||||
action_opcode,
|
||||
is_password: false,
|
||||
is_progressbar,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
action_payload: initial_setting,
|
||||
units: checked_units,
|
||||
show_legend,
|
||||
}
|
||||
}
|
||||
pub fn set_is_password(&mut self, setting: bool) {
|
||||
// this will cause text to be inverted. Untrusted entities can try to set this,
|
||||
// but the GAM should defeat this for dialog boxes outside of the trusted boot
|
||||
// set because they can't achieve a high enough trust level.
|
||||
self.is_password = true;
|
||||
}
|
||||
pub fn set_state(&mut self, state: u32) {
|
||||
if state < self.min {
|
||||
self.action_payload = self.min;
|
||||
} else if state > self.max {
|
||||
self.action_payload = self.max;
|
||||
} else {
|
||||
self.action_payload = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ActionApi for Slider {
|
||||
fn height(&self, glyph_height: i16, margin: i16) -> i16 {
|
||||
/*
|
||||
margin
|
||||
min max <- glyph height
|
||||
-----O---------- <- glyph height
|
||||
[ Okay ] <- glyph height
|
||||
legend
|
||||
margin
|
||||
*/
|
||||
glyph_height * 3 + margin * 2
|
||||
if self.show_legend {
|
||||
glyph_height * 3 + margin * 2
|
||||
} else {
|
||||
glyph_height * 2 + margin * 2
|
||||
}
|
||||
}
|
||||
fn set_action_opcode(&mut self, op: u32) {self.action_opcode = op}
|
||||
|
||||
fn redraw(&self, at_height: i16, modal: &Modal) {
|
||||
let color = if self.is_password {
|
||||
PixelColor::Light
|
||||
} else {
|
||||
PixelColor::Dark
|
||||
};
|
||||
let fill_color = if self.is_password {
|
||||
PixelColor::Dark
|
||||
} else {
|
||||
PixelColor::Light
|
||||
};
|
||||
|
||||
// prime a textview with the correct general style parameters
|
||||
let mut tv = TextView::new(
|
||||
modal.canvas,
|
||||
TextBounds::BoundingBox(Rectangle::new_coords(0, 0, 1, 1))
|
||||
);
|
||||
tv.ellipsis = true;
|
||||
tv.style = modal.style;
|
||||
tv.invert = self.is_password;
|
||||
tv.draw_border= false;
|
||||
tv.margin = Point::new(0, 0,);
|
||||
tv.insertion = None;
|
||||
|
||||
let maxwidth = (modal.canvas_width - modal.margin * 2) as u16;
|
||||
if self.show_legend {
|
||||
/* // min/max doesn't look good, leave it out for now
|
||||
// render min
|
||||
tv.bounds_computed = None;
|
||||
tv.bounds_hint = TextBounds::GrowableFromTl(
|
||||
Point::new(modal.margin, at_height + modal.margin),
|
||||
maxwidth
|
||||
);
|
||||
tv.text.clear();
|
||||
write!(tv, "{}{}", self.min, self.units.to_str()).unwrap();
|
||||
modal.gam.post_textview(&mut tv).expect("couldn't post tv");
|
||||
// render max
|
||||
tv.bounds_computed = None;
|
||||
tv.bounds_hint = TextBounds::GrowableFromBr(
|
||||
Point::new(modal.canvas_width - modal.margin, at_height + modal.margin + modal.line_height),
|
||||
maxwidth
|
||||
);
|
||||
tv.text.clear();
|
||||
write!(tv, "{}{}", self.max, self.units.to_str()).unwrap();
|
||||
modal.gam.post_textview(&mut tv).expect("couldn't post tv");
|
||||
*/
|
||||
// estimate width of current setting
|
||||
tv.bounds_computed = None;
|
||||
tv.bounds_hint = TextBounds::GrowableFromTl(
|
||||
Point::new(0, 0),
|
||||
maxwidth
|
||||
);
|
||||
write!(tv, "{}{}", self.action_payload, self.units.to_str()).unwrap();
|
||||
modal.gam.bounds_compute_textview(&mut tv).expect("couldn't simulate text size");
|
||||
let textwidth = if let Some(bounds) = tv.bounds_computed {
|
||||
bounds.br.x - bounds.tl.x
|
||||
} else {
|
||||
maxwidth as i16
|
||||
};
|
||||
let offset = (modal.canvas_width - textwidth) / 2;
|
||||
// render current setting
|
||||
tv.bounds_computed = None;
|
||||
tv.bounds_hint = TextBounds::GrowableFromTl(
|
||||
Point::new(offset, at_height + modal.margin + modal.line_height*2 + modal.margin),
|
||||
maxwidth
|
||||
);
|
||||
modal.gam.post_textview(&mut tv).expect("couldn't post tv");
|
||||
}
|
||||
|
||||
// the actual slider
|
||||
let outer_rect = Rectangle::new_with_style(
|
||||
Point::new(modal.margin * 2, modal.margin + modal.line_height + at_height),
|
||||
Point::new(modal.canvas_width - modal.margin * 2, modal.margin + modal.line_height * 2 + at_height),
|
||||
DrawStyle::new(fill_color, color, 2)
|
||||
);
|
||||
modal.gam.draw_rectangle(modal.canvas, outer_rect).expect("couldn't draw outer rectangle");
|
||||
let total_width = modal.canvas_width - modal.margin * 4;
|
||||
let slider_point = (((modal.canvas_width - modal.margin * 4) as u32 * (self.action_payload - self.min)) / (self.max - self.min)) as i16;
|
||||
let inner_rect = Rectangle::new_with_style(
|
||||
Point::new(modal.margin * 2, modal.margin + modal.line_height + at_height),
|
||||
Point::new(modal.margin * 2 + slider_point, modal.margin + modal.line_height * 2 + at_height),
|
||||
DrawStyle::new(color, color, 1)
|
||||
);
|
||||
modal.gam.draw_rectangle(modal.canvas, inner_rect).expect("couldn't draw inner rectangle");
|
||||
}
|
||||
fn key_action(&mut self, k: char) -> (Option<xous_ipc::String::<512>>, bool) {
|
||||
log::trace!("key_action: {}", k);
|
||||
if !self.is_progressbar {
|
||||
match k {
|
||||
'←' => {
|
||||
if self.action_payload >= self.min + self.step {
|
||||
self.action_payload -= self.step;
|
||||
} else if self.action_payload >= self.min && self.action_payload < self.min + self.step {
|
||||
self.action_payload = self.min
|
||||
}
|
||||
},
|
||||
'→' => {
|
||||
if self.action_payload <= self.max - self.step {
|
||||
self.action_payload += self.step;
|
||||
} else if self.action_payload < self.max && self.action_payload > self.max - self.step {
|
||||
self.action_payload = self.max
|
||||
}
|
||||
},
|
||||
'\u{0}' => {
|
||||
// ignore null messages
|
||||
}
|
||||
'∴' | '\u{d}' => {
|
||||
send_message(self.action_conn,
|
||||
xous::Message::new_scalar(self.action_opcode as usize, self.action_payload as usize, 0, 0, 0)).expect("couldn't pass on action payload");
|
||||
return(None, true)
|
||||
}
|
||||
_ => {
|
||||
// ignore all other messages
|
||||
}
|
||||
}
|
||||
(None, false)
|
||||
} else {
|
||||
if k == '🛑' { // use the "stop" emoji as a signal that we should close the progress bar
|
||||
(None, true)
|
||||
} else {
|
||||
(None, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait ActionApi {
|
||||
fn height(&self, glyph_height: i16, margin: i16) -> i16 {glyph_height + margin * 2}
|
||||
|
|
@ -891,6 +1064,12 @@ pub struct Modal<'a> {
|
|||
pub style: GlyphStyle,
|
||||
pub helper_data: Option<Buffer<'a>>,
|
||||
pub name: String::<128>,
|
||||
|
||||
// optimize draw time
|
||||
top_dirty: bool,
|
||||
top_memoized_height: Option<i16>,
|
||||
bot_dirty: bool,
|
||||
bot_memoized_height: Option<i16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)]
|
||||
|
|
@ -1047,6 +1226,10 @@ impl<'a> Modal<'a> {
|
|||
style,
|
||||
helper_data: None,
|
||||
name: String::<128>::from_str(name),
|
||||
top_dirty: true,
|
||||
bot_dirty: true,
|
||||
top_memoized_height: None,
|
||||
bot_memoized_height: None,
|
||||
};
|
||||
recompute_canvas(&mut modal, action, top_text, bot_text, style);
|
||||
modal
|
||||
|
|
@ -1073,33 +1256,60 @@ impl<'a> Modal<'a> {
|
|||
xous::create_thread_3(crate::forwarding_thread, addr, size, offset).expect("couldn't spawn a helper thread");
|
||||
}
|
||||
|
||||
pub fn redraw(&self) {
|
||||
pub fn redraw(&mut self) {
|
||||
log::debug!("modal redraw");
|
||||
let canvas_size = self.gam.get_canvas_bounds(self.canvas).unwrap();
|
||||
let do_redraw = self.top_dirty || self.bot_dirty;
|
||||
// draw the outer border
|
||||
self.gam.draw_rounded_rectangle(self.canvas,
|
||||
RoundedRectangle::new(
|
||||
Rectangle::new_with_style(Point::new(0, 0), canvas_size,
|
||||
DrawStyle::new(if self.inverted{PixelColor::Dark} else {PixelColor::Light}, PixelColor::Dark, 3)
|
||||
), 5
|
||||
)).unwrap();
|
||||
if do_redraw {
|
||||
self.gam.draw_rounded_rectangle(self.canvas,
|
||||
RoundedRectangle::new(
|
||||
Rectangle::new_with_style(Point::new(0, 0), canvas_size,
|
||||
DrawStyle::new(if self.inverted{PixelColor::Dark} else {PixelColor::Light}, PixelColor::Dark, 3)
|
||||
), 5
|
||||
)).unwrap();
|
||||
}
|
||||
|
||||
let mut cur_height = self.margin;
|
||||
if let Some(mut tv) = self.top_text {
|
||||
self.gam.post_textview(&mut tv).expect("couldn't draw text");
|
||||
if let Some(bounds) = tv.bounds_computed {
|
||||
cur_height += bounds.br.y - bounds.tl.y;
|
||||
if do_redraw {
|
||||
self.gam.post_textview(&mut tv).expect("couldn't draw text");
|
||||
if let Some(bounds) = tv.bounds_computed {
|
||||
cur_height += bounds.br.y - bounds.tl.y;
|
||||
self.top_memoized_height = Some(bounds.br.y - bounds.tl.y);
|
||||
}
|
||||
self.top_dirty = false;
|
||||
} else {
|
||||
cur_height += self.top_memoized_height.expect("internal error: memoization didn't work correctly");
|
||||
}
|
||||
} else {
|
||||
self.top_dirty = false;
|
||||
}
|
||||
|
||||
let action_height = self.action.height(self.line_height, self.margin);
|
||||
if !do_redraw {
|
||||
// the action area wasn't blanked, so blank it as prep for the action redraw
|
||||
self.gam.draw_rectangle(self.canvas,
|
||||
Rectangle::new_with_style(Point::new(0, cur_height), Point::new(canvas_size.x, cur_height + action_height),
|
||||
DrawStyle::new(if self.inverted{PixelColor::Dark} else {PixelColor::Light}, PixelColor::Dark, 2)
|
||||
)).unwrap();
|
||||
}
|
||||
self.action.redraw(cur_height, &self);
|
||||
cur_height += self.action.height(self.line_height, self.margin);
|
||||
cur_height += action_height;
|
||||
|
||||
if let Some(mut tv) = self.bot_text {
|
||||
self.gam.post_textview(&mut tv).expect("couldn't draw text");
|
||||
if let Some(bounds) = tv.bounds_computed {
|
||||
cur_height += bounds.br.y - bounds.tl.y;
|
||||
if do_redraw {
|
||||
self.gam.post_textview(&mut tv).expect("couldn't draw text");
|
||||
if let Some(bounds) = tv.bounds_computed {
|
||||
cur_height += bounds.br.y - bounds.tl.y;
|
||||
self.bot_memoized_height = Some(bounds.br.y - bounds.tl.y);
|
||||
}
|
||||
self.bot_dirty = false;
|
||||
} else {
|
||||
cur_height += self.bot_memoized_height.expect("internal error: memoization didn't work correctly");
|
||||
}
|
||||
} else {
|
||||
self.bot_dirty = false;
|
||||
}
|
||||
log::trace!("total height: {}", cur_height);
|
||||
self.gam.redraw().unwrap();
|
||||
|
|
@ -1139,11 +1349,19 @@ impl<'a> Modal<'a> {
|
|||
};
|
||||
|
||||
if remove_top {
|
||||
self.top_dirty = true;
|
||||
self.top_text = None;
|
||||
}
|
||||
if remove_bot {
|
||||
self.bot_dirty = true;
|
||||
self.bot_text = None;
|
||||
}
|
||||
if update_top_text.is_some() {
|
||||
self.top_dirty = true;
|
||||
}
|
||||
if update_bot_text.is_some() {
|
||||
self.bot_dirty = true;
|
||||
}
|
||||
|
||||
let mut top_tv_temp = String::<3072>::new(); // size matches that used in TextView
|
||||
if let Some(top_text) = update_top_text {
|
||||
|
|
|
|||
|
|
@ -318,7 +318,8 @@ impl RootKeys {
|
|||
}
|
||||
pub fn get_ux_password_type(&self) -> Option<PasswordType> {self.cur_password_type}
|
||||
|
||||
pub fn is_initialized(&self) -> bool {
|
||||
pub fn is_initialized(&mut self) -> bool {
|
||||
self.keyrom.wfo(utra::keyrom::ADDRESS_ADDRESS, KeyRomLocations::CONFIG as u32);
|
||||
let config = self.keyrom.rf(utra::keyrom::DATA_DATA);
|
||||
if config & keyrom_config::INITIALIZED.ms(1) != 0 {
|
||||
true
|
||||
|
|
@ -345,7 +346,18 @@ impl RootKeys {
|
|||
}
|
||||
|
||||
pub fn do_key_init(&mut self) {
|
||||
|
||||
// here:
|
||||
// - generate signing private key (encrypted with update password)
|
||||
// - generate rootkey (encrypted with boot password)
|
||||
// - generate signing public key
|
||||
// - set the init bit
|
||||
// - sign the loader
|
||||
// - sign the kernel
|
||||
// - compute the patch set for the FPGA bitstream
|
||||
// - do the patch (whatever that means - gotta deal with the AES key, HMAC etc.)
|
||||
// - verify the FPGA image hmac
|
||||
// - sign the FPGA image
|
||||
// - get ready for a reboot
|
||||
}
|
||||
|
||||
pub fn finish_key_init(&mut self) {
|
||||
|
|
|
|||
|
|
@ -139,6 +139,10 @@ fn xmain() -> ! {
|
|||
};
|
||||
let mut dismiss_modal_action = Notification::new(main_cid, Opcode::UxGutter.to_u32().unwrap());
|
||||
dismiss_modal_action.set_is_password(true);
|
||||
let mut progress_action = Slider::new(main_cid, Opcode::UxGutter.to_u32().unwrap(),
|
||||
0, 100, 10, Some("%"), 0, true, true
|
||||
);
|
||||
progress_action.set_is_password(true);
|
||||
|
||||
let mut rootkeys_modal = Modal::new(
|
||||
crate::api::ROOTKEY_MODAL_NAME,
|
||||
|
|
@ -253,16 +257,29 @@ fn xmain() -> ! {
|
|||
PasswordType::Update => {
|
||||
keys.set_ux_password_type(None);
|
||||
// now show the init wait note...
|
||||
dismiss_modal_action.set_action_opcode(Opcode::UxGutter.to_u32().unwrap());
|
||||
rootkeys_modal.modify(
|
||||
Some(ActionType::Notification(dismiss_modal_action)),
|
||||
Some(ActionType::Slider(progress_action)),
|
||||
Some(t!("rootkeys.setup_wait", xous::LANG)), false,
|
||||
None, true, None);
|
||||
rootkeys_modal.activate();
|
||||
|
||||
xous:: yield_slice(); // give some time to the GAM to render
|
||||
xous::yield_slice(); // give some time to the GAM to render
|
||||
|
||||
keys.do_key_init();
|
||||
log::info!("trying out progress bar");
|
||||
let ticktimer = ticktimer_server::Ticktimer::new().unwrap();
|
||||
for i in 1..11 {
|
||||
log::info!("progress: {}", i);
|
||||
progress_action.set_state(i * 10);
|
||||
rootkeys_modal.modify(
|
||||
Some(ActionType::Slider(progress_action)),
|
||||
Some(t!("rootkeys.setup_wait", xous::LANG)), false,
|
||||
None, true, None);
|
||||
rootkeys_modal.redraw();
|
||||
rootkeys_modal.gam.redraw();
|
||||
xous::yield_slice();
|
||||
ticktimer.sleep_ms(3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue