mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-08 04:56:43 +00:00
add some initial selectable list framework
This commit is contained in:
parent
b945a085b0
commit
72e6be8679
4 changed files with 293 additions and 33 deletions
|
|
@ -137,6 +137,12 @@
|
|||
"zh": "FIDO 请求",
|
||||
"en-tts": "FIDO Request"
|
||||
},
|
||||
"vault.select_font": {
|
||||
"en": "Select a font style",
|
||||
"ja": "フォントを選択する",
|
||||
"zh": "选择字体",
|
||||
"en-tts": "Select a font style"
|
||||
},
|
||||
"vault.menu_autotype": {
|
||||
"en": "Autotype",
|
||||
"ja": "自動的に入力",
|
||||
|
|
@ -155,6 +161,12 @@
|
|||
"zh": "删除项目",
|
||||
"en-tts": "Delete item"
|
||||
},
|
||||
"vault.menu_change_font": {
|
||||
"en": "Change font",
|
||||
"ja": "フォントを変更する",
|
||||
"zh": "更改字体",
|
||||
"en-tts": "Change font"
|
||||
},
|
||||
"vault.menu_close": {
|
||||
"en": "Close submenu",
|
||||
"ja": "メニューを閉じる",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod ux;
|
|||
use ux::*;
|
||||
use num_traits::*;
|
||||
use xous_ipc::Buffer;
|
||||
use xous::{send_message, Message};
|
||||
use usbd_human_interface_device::device::fido::*;
|
||||
use std::thread;
|
||||
|
||||
|
|
@ -16,6 +17,8 @@ mod shims;
|
|||
use shims::*;
|
||||
mod submenu;
|
||||
|
||||
use locales::t;
|
||||
|
||||
// CTAP2 testing notes:
|
||||
// run our branch and use this to forward the prompts on to the device:
|
||||
// netcat -k -u -l 6502 > /dev/ttyS0
|
||||
|
|
@ -77,6 +80,7 @@ pub(crate) enum VaultOp {
|
|||
MenuAutotype,
|
||||
MenuEdit,
|
||||
MenuDelete,
|
||||
MenuChangeFont,
|
||||
|
||||
/// exit the application
|
||||
Quit,
|
||||
|
|
@ -160,10 +164,13 @@ fn main() -> ! {
|
|||
let _menu_mgr = submenu::create_submenu(conn, menu_sid);
|
||||
|
||||
let xns = xous_names::XousNames::new().unwrap();
|
||||
// TODO: add a UX loop that indicates we're waiting for a PDDB mount before moving forward
|
||||
let mut vaultux = VaultUx::new(&xns, sid);
|
||||
vaultux.gen_fake_data(0); // THIS IS FOR TESTING ONLY replace with a call that does something "more proper"
|
||||
let mut update_vaultux = true;
|
||||
let mut was_callback = false;
|
||||
let mut allow_redraw = false;
|
||||
let modals = modals::Modals::new(&xns).unwrap();
|
||||
loop {
|
||||
let msg = xous::receive_message(sid).unwrap();
|
||||
log::debug!("got message {:?}", msg);
|
||||
|
|
@ -186,16 +193,16 @@ fn main() -> ! {
|
|||
vaultux.raise_menu();
|
||||
}
|
||||
"↓" => {
|
||||
log::info!("down arrow");
|
||||
vaultux.nav(NavDir::Down);
|
||||
}
|
||||
"↑" => {
|
||||
log::info!("up arrow");
|
||||
vaultux.nav(NavDir::Up);
|
||||
}
|
||||
"←" => {
|
||||
log::info!("left arrow");
|
||||
vaultux.nav(NavDir::PageUp);
|
||||
}
|
||||
"→" => {
|
||||
log::info!("right arrow");
|
||||
vaultux.nav(NavDir::PageDown);
|
||||
}
|
||||
_ => {
|
||||
vaultux.input(s.as_str()).expect("Vault couldn't accept input string");
|
||||
|
|
@ -203,6 +210,9 @@ fn main() -> ! {
|
|||
}
|
||||
update_vaultux = true; // set a flag, instead of calling here, so message can drop and calling server is released
|
||||
was_callback = false;
|
||||
send_message(conn,
|
||||
Message::new_scalar(VaultOp::Redraw.to_usize().unwrap(), 0, 0, 0, 0)
|
||||
).ok();
|
||||
}
|
||||
Some(VaultOp::Redraw) => {
|
||||
if allow_redraw {
|
||||
|
|
@ -230,6 +240,19 @@ fn main() -> ! {
|
|||
Some(VaultOp::MenuEdit) => {
|
||||
log::info!("got edit");
|
||||
}
|
||||
Some(VaultOp::MenuChangeFont) => {
|
||||
for item in FONT_LIST {
|
||||
modals
|
||||
.add_list_item(item)
|
||||
.expect("couldn't build radio item list");
|
||||
}
|
||||
match modals.get_radiobutton(t!("vault.select_font", xous::LANG)) {
|
||||
Ok(style) => {
|
||||
vaultux.set_glyph_style(name_to_style(&style).unwrap_or(DEFAULT_FONT));
|
||||
},
|
||||
_ => log::error!("get_radiobutton failed"),
|
||||
}
|
||||
}
|
||||
Some(VaultOp::Quit) => {
|
||||
log::error!("got Quit");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,13 @@ pub fn create_submenu(vault_conn: xous::CID, menu_mgr: xous::SID) -> MenuMatic {
|
|||
action_payload: MenuPayload::Scalar([0, 0, 0, 0]),
|
||||
close_on_select: true,
|
||||
});
|
||||
menu_items.push(MenuItem {
|
||||
name: xous_ipc::String::from_str(t!("vault.menu_change_font", xous::LANG)),
|
||||
action_conn: Some(vault_conn),
|
||||
action_opcode: VaultOp::MenuChangeFont.to_u32().unwrap(),
|
||||
action_payload: MenuPayload::Scalar([0, 0, 0, 0]),
|
||||
close_on_select: true,
|
||||
});
|
||||
menu_items.push(MenuItem {
|
||||
name: xous_ipc::String::from_str(t!("vault.menu_close", xous::LANG)),
|
||||
action_conn: None,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,45 @@ use gam::{UxRegistration, GlyphStyle};
|
|||
use graphics_server::{Gid, Point, Rectangle, DrawStyle, PixelColor, TextView};
|
||||
use xous::MessageEnvelope;
|
||||
use std::fmt::Write;
|
||||
use pddb::Pddb;
|
||||
use std::cell::RefCell;
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{Read, Write as FsWrite};
|
||||
|
||||
/// Display list for items. "name" is the key by which the list is sorted.
|
||||
/// "extra" is more information about the item, which should not be part of the sort.
|
||||
struct ListItem {
|
||||
name: String,
|
||||
extra: String,
|
||||
}
|
||||
impl ListItem {
|
||||
pub fn clone(&self) -> ListItem {
|
||||
ListItem { name: self.name.to_string(), extra: self.extra.to_string() }
|
||||
}
|
||||
}
|
||||
impl Ord for ListItem {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name.cmp(&other.name)
|
||||
}
|
||||
}
|
||||
impl PartialOrd for ListItem {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
impl PartialEq for ListItem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
impl Eq for ListItem {}
|
||||
|
||||
pub(crate) enum NavDir {
|
||||
Up,
|
||||
Down,
|
||||
PageUp,
|
||||
PageDown,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) struct VaultUx {
|
||||
|
|
@ -20,16 +59,60 @@ pub(crate) struct VaultUx {
|
|||
screensize: Point,
|
||||
margin: Point, // margin to edge of canvas
|
||||
|
||||
// our security token for making changes to our record on the GAM
|
||||
/// our security token for making changes to our record on the GAM
|
||||
token: [u32; 4],
|
||||
|
||||
// current operation mode
|
||||
/// current operation mode
|
||||
mode: VaultMode,
|
||||
|
||||
/// list of all items to be displayed
|
||||
item_list: Vec::<ListItem>,
|
||||
/// list of items displayable after filtering
|
||||
filtered_list: Vec::<ListItem>,
|
||||
/// the index into the item_list that is selected
|
||||
selection_index: usize,
|
||||
|
||||
/// pddb handle
|
||||
pddb: RefCell::<Pddb>,
|
||||
|
||||
/// current font style
|
||||
style: GlyphStyle,
|
||||
item_height: i16,
|
||||
items_per_screen: i16,
|
||||
}
|
||||
|
||||
pub(crate) const DEFAULT_FONT: GlyphStyle = GlyphStyle::Regular;
|
||||
pub(crate) const FONT_LIST: [&'static str; 6] = [
|
||||
"regular", "mono", "cjk",
|
||||
"bold", "large", "small"
|
||||
];
|
||||
pub(crate) fn name_to_style(name: &str) -> Option<GlyphStyle> {
|
||||
match name {
|
||||
"regular" => Some(GlyphStyle::Regular),
|
||||
"mono" => Some(GlyphStyle::Monospace),
|
||||
"cjk" => Some(GlyphStyle::Cjk),
|
||||
"bold" => Some(GlyphStyle::Bold),
|
||||
"large" => Some(GlyphStyle::Large),
|
||||
"small" => Some(GlyphStyle::Small),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
fn style_to_name(style: &GlyphStyle) -> String {
|
||||
match style {
|
||||
GlyphStyle::Regular => "regular".to_string(),
|
||||
GlyphStyle::Monospace => "mono".to_string(),
|
||||
GlyphStyle::Cjk => "cjk".to_string(),
|
||||
GlyphStyle::Bold => "bold".to_string(),
|
||||
GlyphStyle::Large => "large".to_string(),
|
||||
GlyphStyle::Small => "small".to_string(),
|
||||
_ => "regular".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
const TITLE_HEIGHT: i16 = 26;
|
||||
|
||||
impl VaultUx{
|
||||
const VAULT_CONFIG_DICT: &'static str = "vault.config";
|
||||
const VAULT_CONFIG_KEY_FONT: &'static str = "fontstyle";
|
||||
impl VaultUx {
|
||||
pub(crate) fn new(xns: &xous_names::XousNames, sid: xous::SID) -> Self {
|
||||
let gam = gam::Gam::new(xns).expect("can't connect to GAM");
|
||||
|
||||
|
|
@ -49,6 +132,46 @@ impl VaultUx{
|
|||
let content = gam.request_content_canvas(token).expect("couldn't get content canvas");
|
||||
let screensize = gam.get_canvas_bounds(content).expect("couldn't get dimensions of content canvas");
|
||||
gam.toggle_menu_mode(token).expect("couldnt't toggle menu mode");
|
||||
let margin = Point::new(4, 4);
|
||||
|
||||
let pddb = pddb::Pddb::new();
|
||||
// TODO: put some informative message asking to mount the PDDB if it's not mounted, right now you just get a blank screen.
|
||||
pddb.is_mounted_blocking();
|
||||
// extract the style name from the settings, or update them if never initialized
|
||||
let mut style_name_bytes = [0u8; 32];
|
||||
let style_name = match pddb.get(
|
||||
VAULT_CONFIG_DICT,
|
||||
VAULT_CONFIG_KEY_FONT,
|
||||
None, true, true,
|
||||
Some(32), None::<fn()>
|
||||
) {
|
||||
Ok(mut style_key) => {
|
||||
style_key.read(&mut style_name_bytes).ok();
|
||||
String::from_utf8_lossy(&style_name_bytes)
|
||||
}
|
||||
_ => panic!("PDDB access erorr"),
|
||||
};
|
||||
let style = match name_to_style(style_name.as_ref()) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
match pddb.get(
|
||||
VAULT_CONFIG_DICT,
|
||||
VAULT_CONFIG_KEY_FONT,
|
||||
None, true, true,
|
||||
Some(32), None::<fn()>
|
||||
) {
|
||||
Ok(mut style_key) => {
|
||||
style_key.write(style_to_name(&DEFAULT_FONT).as_bytes()).ok();
|
||||
}
|
||||
_ => panic!("PDDB access erorr"),
|
||||
};
|
||||
GlyphStyle::Regular
|
||||
},
|
||||
};
|
||||
let available_height = screensize.y - TITLE_HEIGHT;
|
||||
let glyph_height = gam.glyph_height_hint(style).unwrap();
|
||||
let item_height = (glyph_height * 2) as i16 + margin.y * 2 + 2; // +2 because of the border width
|
||||
let items_per_screen = available_height / item_height;
|
||||
|
||||
VaultUx {
|
||||
input: None,
|
||||
|
|
@ -56,12 +179,76 @@ impl VaultUx{
|
|||
content,
|
||||
gam,
|
||||
screensize,
|
||||
margin: Point::new(4, 4),
|
||||
margin,
|
||||
token,
|
||||
mode: VaultMode::Fido,
|
||||
item_list: Vec::new(),
|
||||
selection_index: 0,
|
||||
filtered_list: Vec::new(),
|
||||
pddb: RefCell::new(pddb),
|
||||
style,
|
||||
item_height,
|
||||
items_per_screen,
|
||||
}
|
||||
}
|
||||
pub(crate) fn set_mode(&mut self, mode: VaultMode) {
|
||||
self.item_list.clear();
|
||||
match mode {
|
||||
VaultMode::Fido | VaultMode::Password => self.gen_fake_data(0),
|
||||
VaultMode::Totp => self.gen_fake_data(1),
|
||||
}
|
||||
self.item_list.sort();
|
||||
self.selection_index = 0;
|
||||
self.filter("");
|
||||
self.mode = mode;
|
||||
}
|
||||
pub(crate) fn set_glyph_style(&mut self, style: GlyphStyle) {
|
||||
self.pddb.borrow().delete_key(VAULT_CONFIG_DICT, VAULT_CONFIG_KEY_FONT, None)
|
||||
.expect("couldn't delete previous setting");
|
||||
|
||||
match self.pddb.borrow().get(
|
||||
VAULT_CONFIG_DICT,
|
||||
VAULT_CONFIG_KEY_FONT,
|
||||
None, true, true,
|
||||
Some(32), None::<fn()>
|
||||
) {
|
||||
Ok(mut style_key) => {
|
||||
style_key.write(style_to_name(&style).as_bytes()).ok();
|
||||
}
|
||||
_ => panic!("PDDB access erorr"),
|
||||
};
|
||||
self.style = style;
|
||||
let available_height = self.screensize.y - TITLE_HEIGHT;
|
||||
let glyph_height = self.gam.glyph_height_hint(self.style).unwrap();
|
||||
self.item_height = (glyph_height * 2) as i16 + self.margin.y * 2 + 2; // +2 because of the border width
|
||||
self.items_per_screen = available_height / self.item_height;
|
||||
}
|
||||
pub(crate) fn nav(&mut self, dir: NavDir) {
|
||||
match dir {
|
||||
NavDir::Up => {
|
||||
if self.selection_index > 0 {self.selection_index -= 1;}
|
||||
}
|
||||
NavDir::Down => {
|
||||
if self.selection_index < self.filtered_list.len() - 1 {
|
||||
self.selection_index += 1;
|
||||
}
|
||||
}
|
||||
NavDir::PageUp => {
|
||||
if self.selection_index > self.items_per_screen as usize {
|
||||
self.selection_index -= self.items_per_screen as usize;
|
||||
} else {
|
||||
self.selection_index = 0;
|
||||
}
|
||||
}
|
||||
NavDir::PageDown => {
|
||||
if self.selection_index < self.filtered_list.len() - 1 - self.items_per_screen as usize {
|
||||
self.selection_index += self.items_per_screen as usize;
|
||||
} else {
|
||||
self.selection_index = self.filtered_list.len() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn set_mode(&mut self, mode: VaultMode) { self.mode = mode; }
|
||||
/// accept a new input string
|
||||
pub(crate) fn input(&mut self, line: &str) -> Result<(), xous::Error> {
|
||||
self.input = Some(String::from(line));
|
||||
|
|
@ -110,49 +297,39 @@ impl VaultUx{
|
|||
self.gam.post_textview(&mut title_text).expect("couldn't post title");
|
||||
|
||||
// ---- draw list body area ----
|
||||
let available_height = self.screensize.y - TITLE_HEIGHT;
|
||||
let style = GlyphStyle::Large;
|
||||
let glyph_height = self.gam.glyph_height_hint(style).unwrap();
|
||||
let box_height = (glyph_height * 2) as i16 + self.margin.y * 2 + 2; // +2 because of the border width
|
||||
let line_count = available_height / box_height;
|
||||
|
||||
let mut test_list = Vec::new();
|
||||
for i in 0..16 {
|
||||
let test_string = format!("Test item {}\nMore info about the item.", i);
|
||||
test_list.push(test_string);
|
||||
}
|
||||
|
||||
// line up the list to justify to the bottom of the screen, based on the actual font requested
|
||||
let available_height = self.screensize.y - TITLE_HEIGHT;
|
||||
let fitting_items = available_height / box_height;
|
||||
let items_height = fitting_items * box_height;
|
||||
let items_height = self.items_per_screen * self.item_height;
|
||||
let mut insert_at = 1 + self.screensize.y - items_height; // +1 to get the border to overlap at the bottom
|
||||
|
||||
let selected = 2;
|
||||
for (index, item) in test_list.iter().enumerate() {
|
||||
if insert_at - 1 > self.screensize.y - box_height { // -1 because of the overlapping border
|
||||
let page = self.selection_index as i16 / self.items_per_screen;
|
||||
let selected = self.selection_index as i16 % self.items_per_screen;
|
||||
for (index, item) in self.filtered_list[
|
||||
((page as usize) * self.items_per_screen as usize).min(self.filtered_list.len()) ..
|
||||
((1 + page as usize) * self.items_per_screen as usize).min(self.filtered_list.len())
|
||||
].iter().enumerate() {
|
||||
if insert_at - 1 > self.screensize.y - self.item_height { // -1 because of the overlapping border
|
||||
break;
|
||||
}
|
||||
let mut box_text = TextView::new(self.content,
|
||||
graphics_server::TextBounds::BoundingBox(
|
||||
Rectangle::new(
|
||||
Point::new(0, insert_at),
|
||||
Point::new(self.screensize.x, insert_at + box_height)
|
||||
Point::new(self.screensize.x, insert_at + self.item_height)
|
||||
)
|
||||
)
|
||||
);
|
||||
box_text.draw_border = true;
|
||||
box_text.rounded_border = None;
|
||||
box_text.clear_area = true;
|
||||
box_text.style = style;
|
||||
box_text.style = self.style;
|
||||
box_text.margin = self.margin;
|
||||
if index == selected {
|
||||
if index == selected as usize {
|
||||
box_text.border_width = 4;
|
||||
}
|
||||
write!(box_text, "{}", item).ok();
|
||||
write!(box_text, "{}\n{}", item.name, item.extra).ok();
|
||||
self.gam.post_textview(&mut box_text).expect("couldn't post list item");
|
||||
|
||||
insert_at += box_height;
|
||||
insert_at += self.item_height;
|
||||
}
|
||||
|
||||
log::trace!("vault app redraw##");
|
||||
|
|
@ -163,4 +340,45 @@ impl VaultUx{
|
|||
pub(crate) fn raise_menu(&self) {
|
||||
self.gam.raise_menu(gam::APP_MENU_0_VAULT).expect("couldn't raise our submenu");
|
||||
}
|
||||
|
||||
pub(crate) fn filter(&mut self, criteria: &str) {
|
||||
self.filtered_list.clear();
|
||||
for item in self.item_list.iter() {
|
||||
if item.name.starts_with(criteria) {
|
||||
self.filtered_list.push(item.clone());
|
||||
}
|
||||
}
|
||||
if self.selection_index >= self.filtered_list.len() {
|
||||
self.selection_index = self.filtered_list.len() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// populates the display list with testing data
|
||||
pub(crate) fn gen_fake_data(&mut self, set: usize) {
|
||||
if set == 0 {
|
||||
self.item_list.push(ListItem { name: "test.com".to_string(), extra: "Used 5 mins ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "google.com".to_string(), extra: "Never used".to_string() });
|
||||
self.item_list.push(ListItem { name: "my app".to_string(), extra: "Used 2 hours ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "💎🙌".to_string(), extra: "Used 2 days ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "百度".to_string(), extra: "Used 1 month ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "duplicate.com".to_string(), extra: "Used 1 week ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "duplicate.com".to_string(), extra: "Used 8 mins ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "amazon.com".to_string(), extra: "Used 3 days ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "ziggyziggyziggylongdomain.com".to_string(), extra: "Never used".to_string() });
|
||||
self.item_list.push(ListItem { name: "another long domain name.com".to_string(), extra: "Used 2 months ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "bunniestudios.com".to_string(), extra: "Used 30 mins ago".to_string() });
|
||||
self.item_list.push(ListItem { name: "github.com".to_string(), extra: "Used 6 hours ago".to_string() });
|
||||
} else {
|
||||
self.item_list.push(ListItem { name: "gmail.com".to_string(), extra: "162 321".to_string() });
|
||||
self.item_list.push(ListItem { name: "google.com".to_string(), extra: "445 768".to_string() });
|
||||
self.item_list.push(ListItem { name: "my 图片 app".to_string(), extra: "982 111".to_string() });
|
||||
self.item_list.push(ListItem { name: "🍕🍔🍟🌭".to_string(), extra: "056 182".to_string() });
|
||||
self.item_list.push(ListItem { name: "百度".to_string(), extra: "111 111".to_string() });
|
||||
self.item_list.push(ListItem { name: "duplicate.com".to_string(), extra: "462 124".to_string() });
|
||||
self.item_list.push(ListItem { name: "duplicate.com".to_string(), extra: "462 124".to_string() });
|
||||
self.item_list.push(ListItem { name: "amazon.com".to_string(), extra: "842 012".to_string() });
|
||||
self.item_list.push(ListItem { name: "ziggyziggyziggylongdomain.com".to_string(), extra: "462 212".to_string() });
|
||||
self.item_list.push(ListItem { name: "github.com".to_string(), extra: "Used 6 hours ago".to_string() });
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue