From 030eeb972d5bf74d454bad062e5fe1c3c49463b7 Mon Sep 17 00:00:00 2001 From: tor Date: Wed, 2 Nov 2022 23:40:52 +0100 Subject: [PATCH] made text and lists more clickable --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/app.rs | 40 ++++- src/main.rs | 8 + src/tabs/add_card/logic.rs | 19 +-- src/tabs/browse/logic.rs | 20 +-- src/tabs/import/logic.rs | 8 +- src/tabs/incread/logic.rs | 64 +++----- src/tabs/review/logic.rs | 20 +-- src/utils/libextensions.rs | 319 +++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 1 + src/utils/statelist.rs | 35 ++-- src/widgets/ankimporter.rs | 20 ++- src/widgets/filepicker.rs | 13 +- src/widgets/find_card.rs | 9 +- src/widgets/load_cards.rs | 8 +- src/widgets/newchild.rs | 11 +- src/widgets/textinput.rs | 45 +++++- 18 files changed, 506 insertions(+), 138 deletions(-) create mode 100644 src/utils/libextensions.rs diff --git a/Cargo.lock b/Cargo.lock index 62dbaac..dbaa0dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2419,7 +2419,7 @@ dependencies = [ [[package]] name = "speki" -version = "0.4.1" +version = "0.4.2" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index da8a67c..04a7282 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "speki" -version = "0.4.2" +version = "0.4.3" edition = "2021" authors = ["Tor Berge torberge@outlook.com"] license = "GPL-2.0-only" diff --git a/src/app.rs b/src/app.rs index 478aa28..3413463 100644 --- a/src/app.rs +++ b/src/app.rs @@ -114,7 +114,7 @@ impl TabsState { fn keyhandler(&mut self, appdata: &AppData, key: MyKey) { match key { MyKey::Nav(dir) => self.tabs[self.index].navigate(dir), - key => self.tabs[self.index].keyhandler(appdata, key), + key => self.tabs[self.index].main_keyhandler(appdata, key), } } fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect) { @@ -261,7 +261,7 @@ pub trait PopUp: Tab { area.height -= 4; area.width -= 4; } - self.render(f, appdata, area); + self.main_render(f, appdata, area); } } @@ -278,16 +278,41 @@ pub trait Widget { pub trait Tab { fn get_title(&self) -> String; - fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect); - fn keyhandler(&mut self, appdata: &AppData, key: MyKey); fn get_manual(&self) -> String { String::new() } fn set_selection(&mut self, area: Rect); - fn get_cursor(&self) -> (u16, u16); - fn navigate(&mut self, dir: NavDir); + + fn get_cursor(&mut self) -> (u16, u16) { + self.get_view().clone().cursor + } + fn navigate(&mut self, dir: NavDir) { + if let Some(popup) = self.get_popup() { + popup.navigate(dir); + } else { + self.get_view().navigate(dir); + } + } + + fn get_view(&mut self) -> &mut View; + + fn main_keyhandler(&mut self, appdata: &AppData, key: MyKey) { + if let Some(popup) = self.get_popup() { + popup.main_keyhandler(appdata, key); + return; + } + if let MyKey::KeyPress(pos) = key.clone() { + self.get_view().cursor = pos; + } + match key { + MyKey::Nav(dir) => self.navigate(dir), + key => self.keyhandler(appdata, key), + } + } + fn keyhandler(&mut self, appdata: &AppData, key: MyKey); fn main_render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect) { + self.set_selection(area); self.render(f, appdata, area); if let Some(popup) = self.get_popup() { if popup.should_quit() { @@ -297,6 +322,9 @@ pub trait Tab { popup.render_popup(f, appdata, area); } } + + fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect); + fn get_popup(&mut self) -> Option<&mut Box> { None } diff --git a/src/main.rs b/src/main.rs index 797e53e..2c39140 100644 --- a/src/main.rs +++ b/src/main.rs @@ -112,6 +112,8 @@ pub enum MyKey { KeyPress((u16, u16)), ScrollUp, ScrollDown, + ScrollLeft, + ScrollRight, } #[derive(Clone, PartialEq, Debug)] @@ -134,6 +136,12 @@ impl MyKey { if let Mouse(mouse) = event { match mouse.kind { MouseEventKind::Down(_) => return Some(MyKey::KeyPress((mouse.column, mouse.row))), + MouseEventKind::ScrollUp if mouse.modifiers == event::KeyModifiers::SHIFT => { + return Some(MyKey::ScrollLeft) + } + MouseEventKind::ScrollDown if mouse.modifiers == event::KeyModifiers::SHIFT => { + return Some(MyKey::ScrollRight) + } MouseEventKind::ScrollUp => return Some(MyKey::ScrollUp), MouseEventKind::ScrollDown => return Some(MyKey::ScrollDown), _ => {} diff --git a/src/tabs/add_card/logic.rs b/src/tabs/add_card/logic.rs index c512568..ad32ac2 100644 --- a/src/tabs/add_card/logic.rs +++ b/src/tabs/add_card/logic.rs @@ -95,6 +95,10 @@ Add card as unfinished: Alt+u .to_string() } + fn get_view(&mut self) -> &mut View { + &mut self.view + } + fn set_selection(&mut self, area: Rect) { self.view.areas.clear(); let chunks = split_leftright_by_percent([75, 15], area); @@ -118,12 +122,7 @@ Add card as unfinished: Alt+u use MyKey::*; let cursor = &self.get_cursor(); - if let KeyPress(pos) = key { - self.view.cursor = pos; - } - match key { - Nav(dir) => self.navigate(dir), Alt('f') => self.submit_card(&appdata.conn, true), Alt('u') => self.submit_card(&appdata.conn, false), Alt('g') => { @@ -138,8 +137,7 @@ Add card as unfinished: Alt+u _ => {} } } - fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect) { - self.set_selection(area); + fn render(&mut self, f: &mut Frame, appdata: &AppData, _area: Rect) { let cursor = &self.get_cursor(); self.topics.render(f, appdata, cursor); @@ -147,11 +145,4 @@ Add card as unfinished: Alt+u self.question.render(f, appdata, cursor); self.answer.render(f, appdata, cursor); } - - fn navigate(&mut self, dir: crate::NavDir) { - self.view.navigate(dir); - } - fn get_cursor(&self) -> (u16, u16) { - self.view.cursor - } } diff --git a/src/tabs/browse/logic.rs b/src/tabs/browse/logic.rs index 6e06dae..270ef68 100644 --- a/src/tabs/browse/logic.rs +++ b/src/tabs/browse/logic.rs @@ -362,12 +362,9 @@ impl Tab for Browse { fn get_title(&self) -> String { "Browse".to_string() } - fn get_cursor(&self) -> (u16, u16) { - self.view.cursor - } - fn navigate(&mut self, dir: NavDir) { - self.view.navigate(dir); + fn get_view(&mut self) -> &mut View { + &mut self.view } fn set_selection(&mut self, area: Rect) { @@ -410,9 +407,8 @@ impl Tab for Browse { &mut self, f: &mut tui::Frame, appdata: &crate::app::AppData, - area: Rect, + _area: Rect, ) { - self.set_selection(area); let cursor = &self.view.cursor; self.filters.render(f, appdata, cursor); @@ -444,17 +440,7 @@ impl Tab for Browse { use MyKey::*; let cursor = &self.get_cursor(); - if let Some(popup) = &mut self.popup { - popup.keyhandler(appdata, key); - return; - } - - if let Nav(dir) = key { - self.navigate(dir); - return; - } match key { - KeyPress(pos) => self.view.cursor = pos, Enter | Char(' ') if self.filtered.is_selected(cursor) => { if let Some(item) = self.filtered.take_selected_item() { if !self.selected_ids.contains(&item.id) { diff --git a/src/tabs/import/logic.rs b/src/tabs/import/logic.rs index 28eb755..fe70e4e 100644 --- a/src/tabs/import/logic.rs +++ b/src/tabs/import/logic.rs @@ -1,4 +1,5 @@ use crate::app::{AppData, Tab, Widget}; +use crate::utils::misc::View; use crate::widgets::message_box::draw_message; use crate::widgets::topics::TopicList; use crate::MyKey; @@ -161,6 +162,7 @@ pub struct Importer { selection: Selection, menu: Menu, area: Rect, + view: View, } impl Importer { @@ -175,6 +177,7 @@ impl Importer { selection, menu, area: Rect::default(), + view: View::default(), } } @@ -203,7 +206,10 @@ impl Importer { impl Tab for Importer { fn set_selection(&mut self, _area: Rect) {} - fn get_cursor(&self) -> (u16, u16) { + fn get_view(&mut self) -> &mut crate::utils::misc::View { + &mut self.view + } + fn get_cursor(&mut self) -> (u16, u16) { (0, 0) } fn navigate(&mut self, dir: NavDir) { diff --git a/src/tabs/incread/logic.rs b/src/tabs/incread/logic.rs index 9fa8e32..56d3b85 100644 --- a/src/tabs/incread/logic.rs +++ b/src/tabs/incread/logic.rs @@ -14,7 +14,6 @@ use crate::utils::statelist::StatefulList; use crate::widgets::message_box::draw_message; use crate::widgets::topics::TopicList; use tui::layout::Rect; -use tui::widgets::Clear; use tui::Frame; use crate::utils::incread::IncListItem; @@ -53,9 +52,13 @@ impl Tab for WikiSelect { "Wikipedia selection".to_string() } + fn get_view(&mut self) -> &mut View { + todo!() + } + fn navigate(&mut self, _dir: crate::NavDir) {} - fn get_cursor(&self) -> (u16, u16) { + fn get_cursor(&mut self) -> (u16, u16) { (0, 0) } @@ -79,16 +82,9 @@ impl Tab for WikiSelect { fn set_selection(&mut self, _area: Rect) {} - fn render(&mut self, f: &mut Frame, appdata: &AppData, mut area: Rect) { + fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect) { let cursor = &self.get_cursor(); - if area.height > 10 && area.width > 10 { - area = crate::utils::misc::centered_rect(80, 70, area); - f.render_widget(Clear, area); //this clears out the background - area.x += 2; - area.y += 2; - area.height -= 4; - area.width -= 4; - } + let chunks = split_updown_by_percent([50, 50], area); let (mut msg, mut search) = (chunks[0], chunks[1]); msg.y = search.y - 5; @@ -107,7 +103,6 @@ pub struct MainInc { pub topics: TopicList, pub popup: Option>, view: View, - area: Rect, } use crate::utils::sql::fetch::load_extracts; @@ -132,7 +127,6 @@ impl MainInc { topics, popup, view, - area: Rect::default(), } } @@ -178,12 +172,16 @@ impl MainInc { } impl Tab for MainInc { - fn get_cursor(&self) -> (u16, u16) { - self.view.cursor + fn get_view(&mut self) -> &mut View { + &mut self.view } - fn navigate(&mut self, dir: crate::NavDir) { - self.view.navigate(dir); + fn get_popup(&mut self) -> Option<&mut Box> { + if let Some(popup) = &mut self.popup { + Some(popup) + } else { + None + } } fn set_selection(&mut self, area: Rect) { @@ -233,27 +231,15 @@ make cloze (visual mode): Alt+z .to_string() } + fn exit_popup(&mut self, appdata: &AppData) { + self.popup = None; + self.reload_inc_list(&appdata.conn); + } + fn keyhandler(&mut self, appdata: &AppData, key: MyKey) { use crate::MyKey::*; let cursor = &self.get_cursor(); - if let Some(popup) = &mut self.popup { - popup.keyhandler(appdata, key); - if popup.should_quit() { - self.popup = None; - self.reload_inc_list(&appdata.conn); - } - return; - } - - if let MyKey::Nav(dir) = key { - self.view.navigate(dir); - return; - } else if let MyKey::Alt('a') = &key { - self.create_source(&appdata.conn, "".to_string()); - return; - } - let incfocus = { if let Some(inc) = &mut self.focused { inc.source.is_selected(cursor) @@ -263,7 +249,7 @@ make cloze (visual mode): Alt+z }; match key { - KeyPress(pos) if self.popup.is_none() => self.view.cursor = pos, + MyKey::Alt('a') => self.create_source(&appdata.conn, "".to_string()), Enter if self.extracts.is_selected(cursor) => self.focus_extracts(&appdata.conn), Enter if self.inclist.is_selected(cursor) => self.focus_list(&appdata.conn), Alt('w') => { @@ -290,20 +276,14 @@ make cloze (visual mode): Alt+z } } - fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect) { - self.set_selection(area); + fn render(&mut self, f: &mut Frame, appdata: &AppData, _area: Rect) { let cursor = &self.get_cursor(); if let Some(inc) = &mut self.focused { inc.source.render(f, appdata, cursor); } - self.topics.render(f, appdata, cursor); self.inclist.render(f, appdata, cursor); self.extracts.render(f, appdata, cursor); - - if let Some(popup) = &mut self.popup { - popup.render(f, appdata, area); - } } } diff --git a/src/tabs/review/logic.rs b/src/tabs/review/logic.rs index 0895ce2..92a32c2 100644 --- a/src/tabs/review/logic.rs +++ b/src/tabs/review/logic.rs @@ -365,16 +365,11 @@ impl Tab for MainReview { ReviewMode::Done => {} } } - fn get_cursor(&self) -> (u16, u16) { - self.view.cursor - } - fn navigate(&mut self, dir: crate::NavDir) { - if let Some(popup) = &mut self.popup { - popup.navigate(dir); - } else { - self.view.navigate(dir); - } + + fn get_view(&mut self) -> &mut View { + &mut self.view } + fn get_title(&self) -> String { "Review".to_string() } @@ -390,7 +385,6 @@ impl Tab for MainReview { } fn render(&mut self, f: &mut Frame, appdata: &AppData, area: Rect) { - self.set_selection(area); let cursor = &self.get_cursor(); self.status @@ -422,10 +416,6 @@ impl Tab for MainReview { fn keyhandler(&mut self, appdata: &AppData, key: MyKey) { let cursor = &self.get_cursor(); use MyKey::*; - if let Some(popup) = &mut self.popup { - popup.keyhandler(appdata, key); - return; - } match &mut self.mode { ReviewMode::Done => self.mode_done(appdata, key), @@ -486,7 +476,6 @@ impl Tab for MainReview { _ => {} }, ReviewMode::Pending(rev) | ReviewMode::Review(rev) => match key { - KeyPress(pos) => self.view.cursor = pos, Alt('s') => { rev.cardview.save_state(&appdata.conn); self.random_mode(appdata); @@ -564,7 +553,6 @@ impl Tab for MainReview { _ => {} }, ReviewMode::IncRead(inc) => match key { - KeyPress(pos) => self.view.cursor = pos, Alt('d') => { inc.source.update_text(&appdata.conn); let id = inc.source.id; diff --git a/src/utils/libextensions.rs b/src/utils/libextensions.rs new file mode 100644 index 0000000..f52a612 --- /dev/null +++ b/src/utils/libextensions.rs @@ -0,0 +1,319 @@ +/* + +This is for rewriting some tui-rs files where i needed extra functionalities + + +*/ + +use tui::{ + buffer::Buffer, + layout::{Corner, Rect}, + style::Style, + text::Text, + widgets::{Block, StatefulWidget, Widget}, +}; +use unicode_width::UnicodeWidthStr; +#[derive(Debug, Clone, Default)] +pub struct MyListState { + offset: usize, + selected: Option, +} + +impl MyListState { + pub fn selected(&self) -> Option { + self.selected + } + + pub fn select(&mut self, index: Option) { + self.selected = index; + if index.is_none() { + self.offset = 0; + } + } + pub fn get_offset(&self) -> usize { + self.offset + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct MyListItem<'a> { + content: Text<'a>, + style: Style, +} + +impl<'a> MyListItem<'a> { + pub fn new(content: T) -> MyListItem<'a> + where + T: Into>, + { + MyListItem { + content: content.into(), + style: Style::default(), + } + } + + pub fn style(mut self, style: Style) -> MyListItem<'a> { + self.style = style; + self + } + + pub fn height(&self) -> usize { + self.content.height() + } +} + +/// A widget to display several items among which one can be selected (optional) +/// +/// # Examples +/// +/// ``` +/// # use tui::widgets::{Block, Borders, MyList, MyListItem}; +/// # use tui::style::{Style, Color, Modifier}; +/// let items = [MyListItem::new("Item 1"), MyListItem::new("Item 2"), MyListItem::new("Item 3")]; +/// MyList::new(items) +/// .block(Block::default().title("MyList").borders(Borders::ALL)) +/// .style(Style::default().fg(Color::White)) +/// .highlight_style(Style::default().add_modifier(Modifier::ITALIC)) +/// .highlight_symbol(">>"); +/// ``` +#[derive(Debug, Clone)] +pub struct MyList<'a> { + block: Option>, + items: Vec>, + /// Style used as a base style for the widget + style: Style, + start_corner: Corner, + /// Style used to render selected item + highlight_style: Style, + /// Symbol in front of the selected item (Shift all items to the right) + highlight_symbol: Option<&'a str>, + /// Whether to repeat the highlight symbol for each line of the selected item + repeat_highlight_symbol: bool, +} + +impl<'a> MyList<'a> { + pub fn new(items: T) -> MyList<'a> + where + T: Into>>, + { + MyList { + block: None, + style: Style::default(), + items: items.into(), + start_corner: Corner::TopLeft, + highlight_style: Style::default(), + highlight_symbol: None, + repeat_highlight_symbol: false, + } + } + + pub fn block(mut self, block: Block<'a>) -> MyList<'a> { + self.block = Some(block); + self + } + + pub fn style(mut self, style: Style) -> MyList<'a> { + self.style = style; + self + } + + pub fn highlight_symbol(mut self, highlight_symbol: &'a str) -> MyList<'a> { + self.highlight_symbol = Some(highlight_symbol); + self + } + + pub fn highlight_style(mut self, style: Style) -> MyList<'a> { + self.highlight_style = style; + self + } + + pub fn repeat_highlight_symbol(mut self, repeat: bool) -> MyList<'a> { + self.repeat_highlight_symbol = repeat; + self + } + + pub fn start_corner(mut self, corner: Corner) -> MyList<'a> { + self.start_corner = corner; + self + } + + fn get_items_bounds( + &self, + selected: Option, + offset: usize, + max_height: usize, + ) -> (usize, usize) { + let offset = offset.min(self.items.len().saturating_sub(1)); + let mut start = offset; + let mut end = offset; + let mut height = 0; + for item in self.items.iter().skip(offset) { + if height + item.height() > max_height { + break; + } + height += item.height(); + end += 1; + } + + let selected = selected.unwrap_or(0).min(self.items.len() - 1); + while selected >= end { + height = height.saturating_add(self.items[end].height()); + end += 1; + while height > max_height { + height = height.saturating_sub(self.items[start].height()); + start += 1; + } + } + while selected < start { + start -= 1; + height = height.saturating_add(self.items[start].height()); + while height > max_height { + end -= 1; + height = height.saturating_sub(self.items[end].height()); + } + } + (start, end) + } +} + +impl<'a> StatefulWidget for MyList<'a> { + type State = MyListState; + + fn render(mut self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + buf.set_style(area, self.style); + let list_area = match self.block.take() { + Some(b) => { + let inner_area = b.inner(area); + b.render(area, buf); + inner_area + } + None => area, + }; + + if list_area.width < 1 || list_area.height < 1 { + return; + } + + if self.items.is_empty() { + return; + } + let list_height = list_area.height as usize; + + let (start, end) = self.get_items_bounds(state.selected, state.offset, list_height); + state.offset = start; + + let highlight_symbol = self.highlight_symbol.unwrap_or(""); + let blank_symbol = " ".repeat(highlight_symbol.width()); + + let mut current_height = 0; + let has_selection = state.selected.is_some(); + for (i, item) in self + .items + .iter_mut() + .enumerate() + .skip(state.offset) + .take(end - start) + { + let (x, y) = match self.start_corner { + Corner::BottomLeft => { + current_height += item.height() as u16; + (list_area.left(), list_area.bottom() - current_height) + } + _ => { + let pos = (list_area.left(), list_area.top() + current_height); + current_height += item.height() as u16; + pos + } + }; + let area = Rect { + x, + y, + width: list_area.width, + height: item.height() as u16, + }; + let item_style = self.style.patch(item.style); + buf.set_style(area, item_style); + + let is_selected = state.selected.map(|s| s == i).unwrap_or(false); + for (j, line) in item.content.lines.iter().enumerate() { + // if the item is selected, we need to display the hightlight symbol: + // - either for the first line of the item only, + // - or for each line of the item if the appropriate option is set + let symbol = if is_selected && (j == 0 || self.repeat_highlight_symbol) { + highlight_symbol + } else { + &blank_symbol + }; + let (elem_x, max_element_width) = if has_selection { + let (elem_x, _) = buf.set_stringn( + x, + y + j as u16, + symbol, + list_area.width as usize, + item_style, + ); + (elem_x, (list_area.width - (elem_x - x)) as u16) + } else { + (x, list_area.width) + }; + buf.set_spans(elem_x, y + j as u16, line, max_element_width as u16); + } + if is_selected { + buf.set_style(area, self.highlight_style); + } + } + } +} + +impl<'a> Widget for MyList<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + #[derive(Debug, Clone, Default)] + pub struct ListState { + offset: usize, + selected: Option, + } + + impl ListState { + pub fn selected(&self) -> Option { + self.selected + } + + pub fn select(&mut self, index: Option) { + self.selected = index; + if index.is_none() { + self.offset = 0; + } + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct ListItem<'a> { + content: Text<'a>, + style: Style, + } + + impl<'a> ListItem<'a> { + pub fn new(content: T) -> ListItem<'a> + where + T: Into>, + { + ListItem { + content: content.into(), + style: Style::default(), + } + } + + pub fn style(mut self, style: Style) -> ListItem<'a> { + self.style = style; + self + } + + pub fn height(&self) -> usize { + self.content.height() + } + } + + let mut state = MyListState::default(); + StatefulWidget::render(self, area, buf, &mut state); + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b4fba0e..b07e354 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,6 +2,7 @@ pub mod aliases; pub mod card; pub mod incread; pub mod interval; +pub mod libextensions; pub mod misc; pub mod sql; pub mod statelist; diff --git a/src/utils/statelist.rs b/src/utils/statelist.rs index 8b10a94..6a83c9c 100644 --- a/src/utils/statelist.rs +++ b/src/utils/statelist.rs @@ -6,7 +6,7 @@ use tui::{ layout::Rect, style::{Color, Modifier, Style}, text::Spans, - widgets::{Block, Borders, List, ListItem, ListState}, + widgets::{Block, Borders}, Frame, }; @@ -18,11 +18,11 @@ pub trait KeyHandler { } } +use super::libextensions::{MyList, MyListState}; use std::fmt::Display; - #[derive(Clone)] pub struct StatefulList { - pub state: ListState, + pub state: MyListState, pub items: Vec, fixed_fields: bool, area: Rect, @@ -33,7 +33,7 @@ pub struct StatefulList { impl StatefulList { pub fn with_items(title: String, items: Vec) -> StatefulList { let mut thelist = Self { - state: ListState::default(), + state: MyListState::default(), items, fixed_fields: true, area: Rect::default(), @@ -55,7 +55,7 @@ impl StatefulList { pub fn new(title: String) -> StatefulList { let items = Vec::::new(); StatefulList { - state: ListState::default(), + state: MyListState::default(), items, fixed_fields: true, area: Rect::default(), @@ -155,6 +155,20 @@ impl StatefulList { self.state.select(Some(qty - 1)); } } + pub fn keypress(&mut self, appdata: &AppData, pos: (u16, u16)) { + if pos.1 == self.area.y + || pos.0 == self.area.x + || pos.0 == self.area.x + self.area.width - 1 + || pos.1 == self.area.y + self.area.height - 1 + { + return; + } + let selected_index = pos.1 - self.area.y - 1 + self.state.get_offset() as u16; + if selected_index < (self.items.len()) as u16 { + self.state.select(Some(selected_index as usize)); + self.keyhandler(appdata, MyKey::Enter); + } + } } impl Widget for StatefulList { @@ -173,6 +187,7 @@ impl Widget for StatefulList { } match key { + MyKey::KeyPress(pos) => self.keypress(appdata, pos), MyKey::Char('k') | MyKey::Up => self.previous(), MyKey::Char('j') | MyKey::Down => self.next(), MyKey::Char('J') if !self.fixed_fields => self.move_item_down(), @@ -190,18 +205,18 @@ impl Widget for StatefulList { let area = self.get_area(); let selected = View::isitselected(area, cursor); let title = &self.title; - let items: Vec = self + let items: Vec = self .items .iter() .map(|item| { let lines = vec![Spans::from(format!("{}", item))]; - ListItem::new(lines).style(style) + MyListItem::new(lines).style(style) }) .collect(); let bordercolor = if selected { Color::Red } else { Color::White }; let borderstyle = Style::default().fg(bordercolor); - let items = List::new(items).block( + let items = MyList::new(items).block( Block::default() .borders(Borders::ALL) .border_style(borderstyle) @@ -240,9 +255,9 @@ impl StatefulList { } } -use crate::MyType; - +use super::libextensions::MyListItem; use super::misc::View; +use crate::MyType; #[derive(Clone)] pub struct TextItem { diff --git a/src/widgets/ankimporter.rs b/src/widgets/ankimporter.rs index 940d826..d69a3bc 100644 --- a/src/widgets/ankimporter.rs +++ b/src/widgets/ankimporter.rs @@ -1,13 +1,12 @@ use std::collections::HashMap; +use crate::utils::libextensions::MyListState; +use crate::utils::misc::View; use crate::utils::statelist::{KeyHandler, StatefulList}; use crate::widgets::textinput::Field; use crate::MyType; use reqwest; -use tui::{ - layout::{Constraint, Direction::Vertical, Layout}, - widgets::ListState, -}; +use tui::layout::{Constraint, Direction::Vertical, Layout}; use super::button::Button; use crate::MyKey; @@ -53,6 +52,7 @@ pub struct Ankimporter { descmap: HashMap, menu: Menu, pub should_quit: ShouldQuit, + view: View, } #[derive(Clone, PartialEq)] @@ -86,6 +86,7 @@ impl Ankimporter { descmap: HashMap::new(), menu, should_quit: ShouldQuit::No, + view: View::default(), } } @@ -179,7 +180,7 @@ impl Ankimporter { } self.list.items = myvec; - self.list.state = ListState::default(); + self.list.state = MyListState::default(); } } @@ -188,10 +189,15 @@ impl Tab for Ankimporter { "ankimporter".to_string() } fn navigate(&mut self, _dir: crate::NavDir) {} - fn get_cursor(&self) -> (u16, u16) { + fn get_cursor(&mut self) -> (u16, u16) { let area = self.searchterm.get_area(); (area.x, area.y) } + + fn get_view(&mut self) -> &mut crate::utils::misc::View { + &mut self.view + } + fn keyhandler(&mut self, appdata: &AppData, key: MyKey) { match self.menu { Menu::Main => { @@ -221,6 +227,8 @@ impl Tab for Ankimporter { MyKey::Up => { self.list.previous(); } + MyKey::KeyPress(pos) => self.list.keypress(appdata, pos), + key => { self.searchterm.keyhandler(appdata, key); self.list.state.select(None); diff --git a/src/widgets/filepicker.rs b/src/widgets/filepicker.rs index ee0c90b..ce7dcbd 100644 --- a/src/widgets/filepicker.rs +++ b/src/widgets/filepicker.rs @@ -1,14 +1,6 @@ use crate::utils::statelist::KeyHandler; use crate::utils::statelist::StatefulList; use std::path::PathBuf; -use tui::widgets::List; -use tui::widgets::ListItem; - -use tui::{ - style::{Color, Modifier, Style}, - text::Span, - widgets::{Block, Borders}, -}; enum ChosenFile { TextFile(PathBuf), @@ -170,7 +162,9 @@ impl FilePicker { } } - pub fn render(&mut self, f: &mut tui::Frame, area: tui::layout::Rect) { + pub fn render(&mut self, _f: &mut tui::Frame, _area: tui::layout::Rect) { + //self.contents.render(f, appdata, cursor); + /* let mylist = { let items: Vec = self .contents @@ -192,6 +186,7 @@ impl FilePicker { items }; f.render_stateful_widget(mylist, area, &mut self.contents.state); + */ } } use crate::MyType; diff --git a/src/widgets/find_card.rs b/src/widgets/find_card.rs index 61489ec..3571aa5 100644 --- a/src/widgets/find_card.rs +++ b/src/widgets/find_card.rs @@ -1,4 +1,5 @@ use crate::app::{AppData, PopUp, Tab, Widget}; +use crate::utils::misc::View; use crate::utils::sql::fetch::CardQuery; use crate::utils::statelist::{KeyHandler, StatefulList}; use crate::utils::{aliases::*, card::Card, sql::insert::update_both}; @@ -20,6 +21,7 @@ pub struct FindCardWidget { pub purpose: CardPurpose, pub should_quit: bool, area: Rect, + view: View, } #[derive(Clone, PartialEq)] @@ -63,6 +65,7 @@ impl FindCardWidget { purpose, should_quit, area: Rect::default(), + view: View::default(), } } @@ -112,6 +115,10 @@ impl Tab for FindCardWidget { "Find card".to_string() } + fn get_view(&mut self) -> &mut crate::utils::misc::View { + &mut self.view + } + fn navigate(&mut self, _dir: crate::NavDir) {} fn set_selection(&mut self, area: Rect) { @@ -133,7 +140,7 @@ impl Tab for FindCardWidget { self.list.set_area(matchlist); } - fn get_cursor(&self) -> (u16, u16) { + fn get_cursor(&mut self) -> (u16, u16) { let area = self.searchterm.get_area(); (area.x, area.y) } diff --git a/src/widgets/load_cards.rs b/src/widgets/load_cards.rs index 54c53cb..9d60b58 100644 --- a/src/widgets/load_cards.rs +++ b/src/widgets/load_cards.rs @@ -715,12 +715,8 @@ impl Tab for Template { "load cards".to_string() } - fn navigate(&mut self, dir: crate::NavDir) { - self.view.navigate(dir); - } - - fn get_cursor(&self) -> (u16, u16) { - self.view.cursor + fn get_view(&mut self) -> &mut View { + &mut self.view } fn set_selection(&mut self, area: Rect) { diff --git a/src/widgets/newchild.rs b/src/widgets/newchild.rs index 7e9b6bc..682f75d 100644 --- a/src/widgets/newchild.rs +++ b/src/widgets/newchild.rs @@ -11,7 +11,7 @@ use tui::{ use crate::utils::sql::fetch::get_topic_of_inc; use crate::MyKey; -use crate::{MyType, NavDir}; +use crate::MyType; use std::sync::{Arc, Mutex}; @@ -111,7 +111,7 @@ impl Tab for AddChildWidget { .direction(Vertical) .constraints( [ - Constraint::Ratio(2, 10), + Constraint::Length(3), Constraint::Ratio(4, 10), Constraint::Ratio(4, 10), ] @@ -140,12 +140,9 @@ impl Tab for AddChildWidget { self.cardview.question.render(f, appdata, &cursor); self.cardview.answer.render(f, appdata, &cursor); } - fn get_cursor(&self) -> (u16, u16) { - self.view.cursor - } - fn navigate(&mut self, dir: NavDir) { - self.view.navigate(dir); + fn get_view(&mut self) -> &mut View { + &mut self.view } fn keyhandler(&mut self, appdata: &AppData, key: MyKey) { diff --git a/src/widgets/textinput.rs b/src/widgets/textinput.rs index bab2632..21402e8 100644 --- a/src/widgets/textinput.rs +++ b/src/widgets/textinput.rs @@ -28,7 +28,7 @@ GLOSSARY: fn current_... : gets the item that the cursor is positioned at fn get_...: gets the item that is specified in an argument to the function - +TODO: update names like this ^ */ @@ -282,6 +282,40 @@ impl Field { } } + fn keypress(&mut self, pos: (u16, u16)) { + if pos.1 <= self.area.y || pos.0 == self.area.x || pos.1 > self.area.y + self.area.height { + return; + } + let line = self.current_abs_visual_line() - self.scroll as usize; + let yclicked = (pos.1 - self.area.y - 1) as usize; + if line > yclicked { + for _ in 0..(line - yclicked) { + self.visual_up(); + } + } else { + for _ in 0..(yclicked - line) { + self.visual_down(); + } + } + let col = self.current_visual_col(); + let xclicked = (pos.0 - self.area.x - 1) as usize; + + let rightlen = self.current_rowlen() - self.cursor.column; + + if xclicked > col { + let diff = xclicked - col; + let iters = std::cmp::min(diff, rightlen); + for _ in 0..iters { + self.next(); + } + } else { + let diff = col - xclicked; + for _ in 0..diff { + self.prev(); + } + } + } + fn delete_right_of_cursor(&mut self) { let leftext = self.get_text_left_of_position(&self.cursor); self.text[self.cursor.row] = leftext; @@ -793,6 +827,15 @@ impl Widget for Field { } fn keyhandler(&mut self, _appdata: &AppData, key: MyKey) { + match key.clone() { + MyKey::ScrollUp => self.visual_up(), + MyKey::ScrollDown => self.visual_down(), + MyKey::ScrollLeft => self.prev(), + MyKey::ScrollRight => self.next(), + MyKey::KeyPress(pos) => self.keypress(pos), + _ => {} + } + match self.mode { Mode::Normal => self.normal_keyhandler(key), Mode::Insert => self.insert_keyhandler(key),