Skip to content

Commit

Permalink
Implement input field history
Browse files Browse the repository at this point in the history
  • Loading branch information
baskerville committed Dec 20, 2019
1 parent 5cdfe51 commit d4b35b3
Show file tree
Hide file tree
Showing 14 changed files with 118 additions and 38 deletions.
8 changes: 8 additions & 0 deletions doc/MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ Dictionaries will be searched recursively in the `dictionaries` directory. The s

You can select the search target by tapping the label in the bottom bar. You can set the input languages of a dictionary by tapping and holding the target's label. You can then provide a comma-separated list of IETF language tags (e.g.: *en, en-US, en-GB*).

# Input Fields

Tapping an input field will:
- Focus the input field if it isn't.
- Move the cursor under your finger if it is.

Tap and hold inside an input field to bring up the input history menu.

# Annex

## Combination Sequences
Expand Down
1 change: 0 additions & 1 deletion doc/TODO.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
- ePUB renderer: RTL.
- Metadata view.
- Complex/fuzzy search queries?
- Input field completions bar.
- Applications: Notes, Terminal, Browser.
- Use nix's ioctl macros.
29 changes: 26 additions & 3 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fs;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, Receiver, Sender};
use std::process::Command;
use std::collections::{BTreeMap, VecDeque};
use std::collections::{HashMap, BTreeMap, VecDeque};
use std::time::{Duration, Instant};
use failure::{Error, ResultExt};
use fnv::FnvHashMap;
Expand All @@ -14,6 +14,7 @@ use crate::framebuffer::{Framebuffer, KoboFramebuffer, Display, UpdateMode};
use crate::view::{View, Event, EntryId, EntryKind, ViewId, AppCmd};
use crate::view::{render, render_region, render_no_wait, render_no_wait_region, handle_event, expose};
use crate::view::common::{locate, locate_by_id, transfer_notifications, overlapping_rectangle};
use crate::view::common::{toggle_input_history_menu};
use crate::view::frontlight::FrontlightWindow;
use crate::view::menu::{Menu, MenuKind};
use crate::view::dictionary::Dictionary as DictionaryApp;
Expand All @@ -38,6 +39,7 @@ use crate::device::{CURRENT_DEVICE, Orientation, FrontlightKind, INTERNAL_CARD_R
use crate::font::Fonts;

pub const APP_NAME: &str = "Plato";
const INPUT_HISTORY_SIZE: usize = 32;

const CLOCK_REFRESH_INTERVAL: Duration = Duration::from_secs(60);
const BATTERY_REFRESH_INTERVAL: Duration = Duration::from_secs(299);
Expand All @@ -53,6 +55,7 @@ pub struct Context {
pub filename: PathBuf,
pub fonts: Fonts,
pub dictionaries: BTreeMap<String, Dictionary>,
pub input_history: HashMap<ViewId, VecDeque<String>>,
pub frontlight: Box<dyn Frontlight>,
pub battery: Box<dyn Battery>,
pub lightsensor: Box<dyn LightSensor>,
Expand All @@ -71,8 +74,8 @@ impl Context {
let dims = fb.dims();
let rotation = CURRENT_DEVICE.transformed_rotation(fb.rotation());
Context { fb, display: Display { dims, rotation },
settings, metadata, filename, fonts, dictionaries: BTreeMap::new(), battery,
frontlight, lightsensor, notification_index: 0, kb_rect: Rectangle::default(),
settings, metadata, filename, fonts, dictionaries: BTreeMap::new(), input_history: HashMap::new(),
battery, frontlight, lightsensor, notification_index: 0, kb_rect: Rectangle::default(),
plugged: false, covered: false, shared: false, online: false }
}

Expand All @@ -96,6 +99,23 @@ impl Context {
}
}
}

pub fn remember_input(&mut self, text: &str, id: ViewId) {
if text.is_empty() {
return;
}

let history = self.input_history.entry(id)
.or_insert_with(|| VecDeque::new());

if history.front().map(String::as_str) != Some(text) {
history.push_front(text.to_string());
}

if history.len() > INPUT_HISTORY_SIZE {
history.pop_back();
}
}
}

struct Task {
Expand Down Expand Up @@ -831,6 +851,9 @@ pub fn run() -> Result<(), Error> {
tx.send(Event::Render(*flw.rect(), UpdateMode::Gui)).unwrap();
view.children_mut().push(Box::new(flw) as Box<dyn View>);
},
Event::ToggleInputHistoryMenu(id, rect) => {
toggle_input_history_menu(view.as_mut(), id, rect, None, &tx, &mut context);
},
Event::Close(ViewId::Frontlight) => {
if let Some(index) = locate::<FrontlightWindow>(view.as_ref()) {
let rect = *view.child(index).rect();
Expand Down
4 changes: 4 additions & 0 deletions src/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ use crate::view::dictionary::Dictionary;
use crate::view::calculator::Calculator;
use crate::view::sketch::Sketch;
use crate::view::common::{locate, locate_by_id, transfer_notifications, overlapping_rectangle};
use crate::view::common::{toggle_input_history_menu};
use crate::helpers::{load_json, save_json, load_toml, save_toml};
use crate::metadata::{Metadata, METADATA_FILENAME, auto_import};
use crate::settings::{Settings, SETTINGS_PATH};
Expand Down Expand Up @@ -445,6 +446,9 @@ pub fn run() -> Result<(), Error> {
tx.send(Event::Render(*flw.rect(), UpdateMode::Gui)).unwrap();
view.children_mut().push(Box::new(flw) as Box<dyn View>);
},
Event::ToggleInputHistoryMenu(id, rect) => {
toggle_input_history_menu(view.as_mut(), id, rect, None, &tx, &mut context);
},
Event::Close(ViewId::Frontlight) => {
if let Some(index) = locate::<FrontlightWindow>(view.as_ref()) {
let rect = *view.child(index).rect();
Expand Down
4 changes: 2 additions & 2 deletions src/view/calculator/input_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ impl InputBar {
}
}

pub fn set_text(&mut self, text: &str, move_cursor: bool, hub: &Hub) {
pub fn set_text(&mut self, text: &str, move_cursor: bool, hub: &Hub, context: &mut Context) {
if let Some(input_field) = self.children[2].downcast_mut::<InputField>() {
input_field.set_text(text, move_cursor, hub);
input_field.set_text(text, move_cursor, hub, context);
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/view/calculator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ impl Calculator {
}
}

fn history_navigate(&mut self, dir: CycleDir, honor_prefix: bool, hub: &Hub) {
fn history_navigate(&mut self, dir: CycleDir, honor_prefix: bool, hub: &Hub, context: &mut Context) {
let beginning = if honor_prefix {
self.children[4].downcast_ref::<InputBar>().unwrap().text_before_cursor()
} else {
Expand All @@ -378,7 +378,7 @@ impl Calculator {
if let Some(cursor) = cursor_opt {
let line = self.data[cursor].content.as_str();
if let Some(input_bar) = self.children[4].downcast_mut::<InputBar>() {
input_bar.set_text(line, !honor_prefix, hub);
input_bar.set_text(line, !honor_prefix, hub, context);
}
self.history.cursor = cursor;
}
Expand Down Expand Up @@ -486,7 +486,7 @@ impl View for Calculator {
Event::Submit(ViewId::CalculatorInput, ref line) => {
self.append(Line { origin: LineOrigin::Input, content: line.to_string() }, context);
if let Some(input_bar) = self.children[4].downcast_mut::<InputBar>() {
input_bar.set_text("", true, hub);
input_bar.set_text("", true, hub, context);
}
if let Some(stdin) = self.process.stdin.as_mut() {
writeln!(stdin, "{}", line).ok();
Expand All @@ -506,7 +506,7 @@ impl View for Calculator {
true
},
Event::History(dir, honor_prefix) => {
self.history_navigate(dir, honor_prefix, hub);
self.history_navigate(dir, honor_prefix, hub, context);
true
},
Event::Select(EntryId::SetFontSize(v)) => {
Expand Down
24 changes: 24 additions & 0 deletions src/view/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,27 @@ pub fn toggle_clock_menu(view: &mut dyn View, rect: Rectangle, enable: Option<bo
view.children_mut().push(Box::new(clock_menu) as Box<dyn View>);
}
}

pub fn toggle_input_history_menu(view: &mut dyn View, id: ViewId, rect: Rectangle, enable: Option<bool>, hub: &Hub, context: &mut Context) {
if let Some(index) = locate_by_id(view, ViewId::ClockMenu) {
if let Some(true) = enable {
return;
}
hub.send(Event::Expose(*view.child(index).rect(), UpdateMode::Gui)).unwrap();
view.children_mut().remove(index);
} else {
if let Some(false) = enable {
return;
}
let entries = context.input_history.get(&id)
.map(|h| h.iter().map(|s|
EntryKind::Command(s.to_string(),
EntryId::SetInputText(id, s.to_string())))
.collect::<Vec<EntryKind>>());
if let Some(entries) = entries {
let input_history_menu = Menu::new(rect, ViewId::InputHistoryMenu, MenuKind::DropDown, entries, context);
hub.send(Event::Render(*input_history_menu.rect(), UpdateMode::Gui)).unwrap();
view.children_mut().push(Box::new(input_history_menu) as Box<dyn View>);
}
}
}
9 changes: 5 additions & 4 deletions src/view/dictionary/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ impl Dictionary {

let search_bar = SearchBar::new(rect![rect.min.x, rect.min.y + small_height as i32 + big_thickness,
rect.max.x, rect.min.y + 2 * small_height as i32 - small_thickness],
ViewId::DictionarySearchInput,
"", query);
children.push(Box::new(search_bar) as Box<dyn View>);

Expand Down Expand Up @@ -157,7 +158,7 @@ impl Dictionary {
hub.send(Event::Render(rect, UpdateMode::Gui)).unwrap();

if query.is_empty() {
hub.send(Event::Focus(Some(ViewId::SearchInput))).unwrap();
hub.send(Event::Focus(Some(ViewId::DictionarySearchInput))).unwrap();
} else {
hub.send(Event::Define(query.to_string())).unwrap();
}
Expand Down Expand Up @@ -312,7 +313,7 @@ impl Dictionary {
.and_then(|name| context.settings.dictionary.languages.get(name))
.filter(|langs| !langs.is_empty()) {
let (tx, _rx) = mpsc::channel();
edit_languages.set_text(&langs.join(", "), &tx);
edit_languages.set_text(&langs.join(", "), &tx, context);
}

hub.send(Event::Render(*edit_languages.rect(), UpdateMode::Gui)).unwrap();
Expand Down Expand Up @@ -353,7 +354,7 @@ impl Dictionary {
if let Some(query) = text {
self.query = query.to_string();
if let Some(search_bar) = self.children[2].downcast_mut::<SearchBar>() {
search_bar.set_text(query, hub);
search_bar.set_text(query, hub, context);
}
}
let content = query_to_content(&self.query, &self.language, self.fuzzy, self.target.as_ref(), context);
Expand Down Expand Up @@ -403,7 +404,7 @@ impl View for Dictionary {
self.define(Some(query), hub, context);
true
},
Event::Submit(ViewId::SearchInput, ref text) => {
Event::Submit(ViewId::DictionarySearchInput, ref text) => {
if !text.is_empty() {
self.toggle_keyboard(false, None, hub, context);
self.define(Some(text), hub, context);
Expand Down
13 changes: 7 additions & 6 deletions src/view/home/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,8 @@ impl Home {
return;
}

if let Some(ViewId::SearchInput) = self.focus {
self.toggle_keyboard(false, false, Some(ViewId::SearchInput), hub, context);
if let Some(ViewId::HomeSearchInput) = self.focus {
self.toggle_keyboard(false, false, Some(ViewId::HomeSearchInput), hub, context);
}

self.children.drain(index - 1 ..= index);
Expand All @@ -637,6 +637,7 @@ impl Home {
let search_bar = SearchBar::new(rect![self.rect.min.x, sp_rect.max.y,
self.rect.max.x,
sp_rect.max.y + delta_y - thickness],
ViewId::HomeSearchInput,
"Title, author, category",
"");

Expand All @@ -652,10 +653,10 @@ impl Home {
}

if locate::<Keyboard>(self).is_none() {
self.toggle_keyboard(true, false, Some(ViewId::SearchInput), hub, context);
self.toggle_keyboard(true, false, Some(ViewId::HomeSearchInput), hub, context);
}

hub.send(Event::Focus(Some(ViewId::SearchInput))).unwrap();
hub.send(Event::Focus(Some(ViewId::HomeSearchInput))).unwrap();

self.resize_summary(0, false, hub, context);
search_visible = true;
Expand Down Expand Up @@ -1461,7 +1462,7 @@ impl View for Home {
ViewId::RenameCategoryInput,
21, context);
let (tx, _rx) = mpsc::channel();
ren_categ.set_text(categ_old, &tx);
ren_categ.set_text(categ_old, &tx, context);
hub.send(Event::Render(*ren_categ.rect(), UpdateMode::Gui)).unwrap();
hub.send(Event::Focus(Some(ViewId::RenameCategoryInput))).unwrap();
self.children.push(Box::new(ren_categ) as Box<dyn View>);
Expand Down Expand Up @@ -1528,7 +1529,7 @@ impl View for Home {
self.toggle_keyboard(false, true, None, hub, context);
true
},
Event::Submit(ViewId::SearchInput, ref text) => {
Event::Submit(ViewId::HomeSearchInput, ref text) => {
self.query = make_query(text);
if self.query.is_some() {
// TODO: avoid updating things twice
Expand Down
20 changes: 17 additions & 3 deletions src/view/input_field.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::device::CURRENT_DEVICE;
use crate::framebuffer::{Framebuffer, UpdateMode};
use super::{View, Event, Hub, Bus, KeyboardEvent, ViewId, TextKind};
use super::{View, Event, Hub, Bus, KeyboardEvent, ViewId, EntryId, TextKind};
use super::THICKNESS_MEDIUM;
use crate::gesture::GestureEvent;
use crate::font::{Fonts, font_from_style, NORMAL_STYLE, FONT_SIZES};
Expand Down Expand Up @@ -106,9 +106,10 @@ impl InputField {
self
}

pub fn set_text(&mut self, text: &str, move_cursor: bool, hub: &Hub) {
pub fn set_text(&mut self, text: &str, move_cursor: bool, hub: &Hub, context: &mut Context) {
if self.text != text {
self.text = text.to_string();
context.remember_input(text, self.id);
if move_cursor {
self.cursor = self.text.len();
}
Expand Down Expand Up @@ -205,6 +206,10 @@ impl View for InputField {
}
true
},
Event::Gesture(GestureEvent::HoldFingerShort(center, _)) if self.rect.includes(center) => {
hub.send(Event::ToggleInputHistoryMenu(self.id, self.rect)).unwrap();
true
},
Event::Focus(id_opt) => {
let focused = id_opt.is_some() && id_opt.unwrap() == self.id;
if self.focused != focused {
Expand Down Expand Up @@ -241,11 +246,20 @@ impl View for InputField {
},
KeyboardEvent::Submit => {
bus.push_back(Event::Submit(self.id, self.text.clone()));
context.remember_input(&self.text, self.id);
},
};
hub.send(Event::RenderNoWait(self.rect, UpdateMode::Gui)).unwrap();
true
}
},
Event::Select(EntryId::SetInputText(id, ref text)) => {
if self.id == id {
self.set_text(text, true, hub, context);
true
} else {
false
}
},
_ => false,
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ pub enum Event {
Submit(ViewId, String),
Slider(SliderId, f32, FingerStatus),
ToggleNear(ViewId, Rectangle),
ToggleInputHistoryMenu(ViewId, Rectangle),
ToggleBookMenu(Rectangle, usize),
ToggleCategoryMenu(Rectangle, String),
TogglePresetMenu(Rectangle, usize),
Expand Down Expand Up @@ -307,7 +308,7 @@ pub enum AppCmd {
},
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub enum ViewId {
Home,
Reader,
Expand All @@ -319,6 +320,7 @@ pub enum ViewId {
BatteryMenu,
ClockMenu,
SearchTargetMenu,
InputHistoryMenu,
Frontlight,
Dictionary,
FontSizeMenu,
Expand Down Expand Up @@ -352,7 +354,9 @@ pub enum ViewId {
AddCategoriesInput,
RenameCategory,
RenameCategoryInput,
SearchInput,
HomeSearchInput,
ReaderSearchInput,
DictionarySearchInput,
CalculatorInput,
SearchBar,
Keyboard,
Expand Down Expand Up @@ -486,6 +490,7 @@ pub enum EntryId {
SetContrastGray(i32),
SetRotationLock(Option<RotationLock>),
SetSearchTarget(Option<String>),
SetInputText(ViewId, String),
ToggleFuzzy,
ToggleInverted,
ToggleMonochrome,
Expand Down
4 changes: 2 additions & 2 deletions src/view/named_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,9 @@ impl NamedInput {
}
}

pub fn set_text(&mut self, text: &str, hub: &Hub) {
pub fn set_text(&mut self, text: &str, hub: &Hub, context: &mut Context) {
if let Some(input_field) = self.children[1].downcast_mut::<InputField>() {
input_field.set_text(text, true, hub);
input_field.set_text(text, true, hub, context);
}
}
}
Expand Down
Loading

0 comments on commit d4b35b3

Please sign in to comment.