}
}
+pub trait EditableMenuLineData {
+ fn display(&self) -> ColouredString;
+ fn to_text(&self) -> String;
+ fn from_text(text: &str) -> Self;
+}
+
+impl EditableMenuLineData for String {
+ fn display(&self) -> ColouredString { ColouredString::plain(self) }
+ fn to_text(&self) -> String { self.clone() }
+ fn from_text(text: &str) -> Self { text.to_owned() }
+}
+
+impl EditableMenuLineData for Option<String> {
+ fn display(&self) -> ColouredString {
+ match self {
+ None => ColouredString::uniform("none", '0'),
+ Some(ref text) => ColouredString::plain(text),
+ }
+ }
+
+ fn to_text(&self) -> String {
+ match self {
+ None => "".to_owned(),
+ Some(ref text) => text.clone(),
+ }
+ }
+
+ fn from_text(text: &str) -> Self {
+ match text {
+ "" => None,
+ text => Some(text.to_owned()),
+ }
+ }
+}
+
+pub struct EditableMenuLine<Data: EditableMenuLineData> {
+ key: OurKey,
+ description: ColouredString,
+ data: Data,
+ menuline: MenuKeypressLine,
+ prompt: MenuKeypressLine,
+ editor: Option<SingleLineEditor>,
+ last_width: Option<usize>,
+}
+
+impl<Data: EditableMenuLineData> EditableMenuLine<Data> {
+ pub fn new(key: OurKey, description: ColouredString, data: Data) -> Self
+ {
+ let menuline = Self::make_menuline(key, &description, &data);
+ let prompt = Self::make_prompt(key, &description);
+
+ EditableMenuLine {
+ key,
+ description,
+ data,
+ menuline,
+ prompt,
+ editor: None,
+ last_width: None,
+ }
+ }
+
+ fn make_menuline(key: OurKey, description: &ColouredString, data: &Data)
+ -> MenuKeypressLine
+ {
+ let desc = description + data.display();
+ MenuKeypressLine::new(key, desc)
+ }
+
+ fn make_prompt(key: OurKey, description: &ColouredString)
+ -> MenuKeypressLine
+ {
+ MenuKeypressLine::new(key, description.to_owned())
+ }
+
+ pub fn render(&self, width: usize, cursorpos: &mut CursorPosition,
+ cy: usize) -> ColouredString
+ {
+ if let Some(ref editor) = self.editor {
+ let (text, cx) = editor.draw(width);
+ if let Some(cx) = cx {
+ *cursorpos = CursorPosition::At(cx, cy);
+ }
+ text
+ } else {
+ self.menuline.render_oneline(width, None, &DefaultDisplayStyle)
+ }
+ }
+
+ // Returns a LogicalAction just to make it more convenient to put
+ // in matches on keypresses
+ pub fn start_editing(&mut self) -> LogicalAction {
+ self.editor = Some(SingleLineEditor::new(self.data.to_text()));
+ self.refresh_editor_prompt();
+ LogicalAction::Nothing
+ }
+
+ pub fn resize(&mut self, w: usize) {
+ self.last_width = Some(w);
+ self.refresh_editor_prompt();
+ }
+
+ pub fn refresh_editor_prompt(&mut self) {
+ if let Some(ref mut editor) = self.editor {
+ if let Some(w) = self.last_width {
+ let prompt = self.prompt
+ .render_oneline(w, None, &DefaultDisplayStyle);
+ editor.resize(w);
+ editor.set_prompt(prompt);
+ }
+ }
+ }
+
+ pub fn handle_keypress(&mut self, key: OurKey) -> bool {
+ let (consumed, done) = if let Some(ref mut editor) = self.editor {
+ if editor.handle_keypress(key) {
+ self.data = Data::from_text(editor.borrow_text());
+ self.menuline = Self::make_menuline(
+ self.key, &self.description, &self.data);
+ (true, true)
+ } else {
+ (true, false)
+ }
+ } else {
+ (false, false)
+ };
+
+ if done {
+ self.editor = None;
+ }
+
+ consumed
+ }
+
+ pub fn get_data(&self) -> &Data { &self.data }
+ pub fn is_editing(&self) -> bool { self.editor.is_some() }
+}
+
+impl<Data: EditableMenuLineData> MenuKeypressLineGeneral
+for EditableMenuLine<Data> {
+ fn check_widths(&self, lmaxwid: &mut usize, rmaxwid: &mut usize) {
+ self.menuline.check_widths(lmaxwid, rmaxwid);
+ self.prompt.check_widths(lmaxwid, rmaxwid);
+ }
+ fn reset_widths(&mut self) {
+ self.menuline.reset_widths();
+ self.prompt.reset_widths();
+ }
+ fn ensure_widths(&mut self, lmaxwid: usize, rmaxwid: usize) {
+ self.menuline.ensure_widths(lmaxwid, rmaxwid);
+ self.prompt.ensure_widths(lmaxwid, rmaxwid);
+ }
+}
+
pub fn get_user_to_examine() -> Box<dyn ActivityState> {
Box::new(BottomLineEditorOverlay::new(
ColouredString::plain("Examine User: "),
-use itertools::Itertools;
use std::iter::once;
use strum::IntoEnumIterator;
use sys_locale::get_locale;
};
use super::text::*;
use super::types::Visibility;
-use super::editor::SingleLineEditor;
+use super::editor::EditableMenuLine;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PostMetadata {
}
}
-#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-enum PostMenuMode {
- Normal,
- EditCW,
- EditLang,
-}
-use PostMenuMode::*;
-
struct PostMenu {
post: Post,
title: FileHeader,
ml_cancel: MenuKeypressLine,
ml_edit: MenuKeypressLine,
ml_vis: MenuKeypressLine,
- ml_content_warning: MenuKeypressLine,
- ml_language: MenuKeypressLine,
- mode: PostMenuMode,
- editor: Option<SingleLineEditor>,
- editor_prompt: Option<MenuKeypressLine>,
- last_width: usize,
+ el_content_warning: EditableMenuLine<Option<String>>,
+ el_language: EditableMenuLine<String>,
}
impl PostMenu {
let ml_edit = MenuKeypressLine::new(
Pr('A'), ColouredString::plain("Re-edit post"));
let ml_vis = Self::visibility_item(post.m.visibility);
- let ml_content_warning = Self::content_warning_item(
- post.m.content_warning.as_deref());
- let ml_language = Self::language_item(&post.m.language);
+ let el_content_warning = EditableMenuLine::new(
+ Pr('W'), ColouredString::plain("Content warning: "),
+ post.m.content_warning.clone());
+ let el_language = EditableMenuLine::new(
+ Pr('L'), ColouredString::plain("Language: "),
+ post.m.language.clone());
let mut pm = PostMenu {
post,
ml_cancel,
ml_edit,
ml_vis,
- ml_content_warning,
- ml_language,
- mode: Normal,
- editor: None,
- editor_prompt: None,
- last_width: 0,
+ el_content_warning,
+ el_language,
};
pm.fix_widths();
pm
self.ml_post.check_widths(&mut lmaxwid, &mut rmaxwid);
self.ml_cancel.check_widths(&mut lmaxwid, &mut rmaxwid);
self.ml_edit.check_widths(&mut lmaxwid, &mut rmaxwid);
- self.ml_content_warning.check_widths(&mut lmaxwid, &mut rmaxwid);
- self.ml_language.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.el_content_warning.check_widths(&mut lmaxwid, &mut rmaxwid);
+ self.el_language.check_widths(&mut lmaxwid, &mut rmaxwid);
for vis in Visibility::iter() {
Self::visibility_item(vis).check_widths(&mut lmaxwid, &mut rmaxwid);
}
- if let Some(ref ml) = self.editor_prompt {
- ml.check_widths(&mut lmaxwid, &mut rmaxwid);
- }
self.ml_post.reset_widths();
self.ml_cancel.reset_widths();
self.ml_edit.reset_widths();
self.ml_vis.reset_widths();
- self.ml_content_warning.reset_widths();
- self.ml_language.reset_widths();
- if let Some(ref mut ml) = self.editor_prompt {
- ml.reset_widths();
- }
+ self.el_content_warning.reset_widths();
+ self.el_language.reset_widths();
self.ml_post.ensure_widths(lmaxwid, rmaxwid);
self.ml_cancel.ensure_widths(lmaxwid, rmaxwid);
self.ml_edit.ensure_widths(lmaxwid, rmaxwid);
self.ml_vis.ensure_widths(lmaxwid, rmaxwid);
- self.ml_content_warning.ensure_widths(lmaxwid, rmaxwid);
- self.ml_language.ensure_widths(lmaxwid, rmaxwid);
- if let Some(ref mut ml) = self.editor_prompt {
- ml.ensure_widths(lmaxwid, rmaxwid);
- }
+ self.el_content_warning.ensure_widths(lmaxwid, rmaxwid);
+ self.el_language.ensure_widths(lmaxwid, rmaxwid);
(lmaxwid, rmaxwid)
}
MenuKeypressLine::new(Pr('V'), text)
}
- fn content_warning_item(warning: Option<&str>) -> MenuKeypressLine {
- let text = match warning {
- None => ColouredString::general(
- "Content warning: none",
- " 0000"),
- Some(text) => ColouredString::plain(
- &format!("Content warning: {text}")),
- };
- MenuKeypressLine::new(Pr('W'), text)
- }
-
- fn language_item(lang: &str) -> MenuKeypressLine {
- let text = ColouredString::plain(&format!("Language: {lang}"));
- MenuKeypressLine::new(Pr('L'), text)
- }
-
fn cycle_visibility(&mut self) -> LogicalAction {
self.post.m.visibility = match self.post.m.visibility {
Visibility::Public => Visibility::Unlisted,
LogicalAction::Nothing
}
- fn setup_editor(&mut self, prompt: MenuKeypressLine, text: String,
- mode: PostMenuMode) {
- self.editor_prompt = Some(prompt);
- self.fix_widths();
-
- let mut ed = SingleLineEditor::new(text);
- ed.resize(self.last_width);
- self.editor = Some(ed);
-
- self.mode = mode;
- self.set_editor_prompt();
- }
-
- fn set_editor_prompt(&mut self) {
- if let Some(ref mut ed) = &mut self.editor {
- ed.set_prompt(
- self.editor_prompt.as_ref().expect("editor goes with prompt")
- .render(self.last_width)
- .iter()
- .exactly_one()
- .expect("MenuKeypressLine should return exactly 1 line")
- .clone());
- ed.resize(self.last_width);
- }
- }
+ fn post(&mut self, client: &mut Client) -> LogicalAction {
+ self.post.m.content_warning =
+ self.el_content_warning.get_data().clone();
+ self.post.m.language = self.el_language.get_data().clone();
- fn edit_content_warning(&mut self) -> LogicalAction {
- let text = match &self.post.m.content_warning {
- Some(s) => s.clone(),
- None => "".to_owned(),
- };
- self.setup_editor(Self::content_warning_item(Some("")), text, EditCW);
- LogicalAction::Nothing
- }
- fn edit_language(&mut self) -> LogicalAction {
- self.setup_editor(Self::language_item(""),
- self.post.m.language.clone(), EditLang);
- LogicalAction::Nothing
- }
-
- fn post(&self, client: &mut Client) -> LogicalAction {
match client.post_status(&self.post) {
Ok(_) => LogicalAction::Pop,
Err(_) => LogicalAction::Beep, // FIXME: report the error!
lines.extend_from_slice(&self.ml_edit.render(w));
lines.extend_from_slice(&BlankLine::render_static());
lines.extend_from_slice(&self.ml_vis.render(w));
-
- if self.mode == EditCW {
- let (text, cx) = self.editor.as_ref().unwrap().draw(w);
- if let Some(cx) = cx {
- cursorpos = CursorPosition::At(cx, lines.len());
- }
- lines.push(text);
- } else {
- lines.extend_from_slice(&self.ml_content_warning.render(w));
- }
-
- if self.mode == EditLang {
- let (text, cx) = self.editor.as_ref().unwrap().draw(w);
- if let Some(cx) = cx {
- cursorpos = CursorPosition::At(cx, lines.len());
- }
- lines.push(text);
- } else {
- lines.extend_from_slice(&self.ml_language.render(w));
- }
+ lines.push(self.el_content_warning.render(
+ w, &mut cursorpos, lines.len()));
+ lines.push(self.el_language.render(
+ w, &mut cursorpos, lines.len()));
while lines.len() + 1 < h {
lines.extend_from_slice(&BlankLine::render_static());
}
- lines.extend_from_slice(&match self.mode {
- Normal => self.normal_status.render(w),
- _ => self.edit_status.render(w),
- });
+ if self.el_content_warning.is_editing() ||
+ self.el_language.is_editing()
+ {
+ lines.extend_from_slice(&self.edit_status.render(w));
+ } else {
+ lines.extend_from_slice(&self.normal_status.render(w));
+ }
(lines, cursorpos)
}
fn handle_keypress(&mut self, key: OurKey, client: &mut Client) ->
LogicalAction
{
- match self.mode {
- Normal => match key {
- Space => self.post(client),
- Pr('q') | Pr('Q') => LogicalAction::Pop,
- Pr('a') | Pr('A') => LogicalAction::PostReEdit(
- self.post.clone()),
- Pr('v') | Pr('V') => self.cycle_visibility(),
- Pr('w') | Pr('W') => self.edit_content_warning(),
- Pr('l') | Pr('L') => self.edit_language(),
- _ => LogicalAction::Nothing,
- }
-
- EditCW => {
- if self.editor.as_mut().unwrap().handle_keypress(key) {
- self.post.m.content_warning =
- match self.editor.as_ref().unwrap().borrow_text() {
- "" => None,
- s => Some(s.to_owned()),
- };
- self.ml_content_warning = Self::content_warning_item(
- self.post.m.content_warning.as_deref());
-
- self.mode = Normal;
- self.editor = None;
- self.editor_prompt = None;
-
- self.fix_widths();
- }
- LogicalAction::Nothing
- }
-
- EditLang => {
- if self.editor.as_mut().unwrap().handle_keypress(key) {
- self.post.m.language = self.editor.as_ref().unwrap()
- .borrow_text().to_owned();
- self.ml_language = Self::language_item(
- &self.post.m.language);
-
- self.mode = Normal;
- self.editor = None;
- self.editor_prompt = None;
-
- self.fix_widths();
- }
- LogicalAction::Nothing
- }
+ // Let editable menu lines have first crack at the keypress
+ if self.el_content_warning.handle_keypress(key) ||
+ self.el_language.handle_keypress(key)
+ {
+ self.fix_widths();
+ return LogicalAction::Nothing;
+ }
+
+ match key {
+ Space => self.post(client),
+ Pr('q') | Pr('Q') => LogicalAction::Pop,
+ Pr('a') | Pr('A') => LogicalAction::PostReEdit(
+ self.post.clone()),
+ Pr('v') | Pr('V') => self.cycle_visibility(),
+ Pr('w') | Pr('W') => self.el_content_warning.start_editing(),
+ Pr('l') | Pr('L') => self.el_language.start_editing(),
+ _ => LogicalAction::Nothing,
}
}
fn resize(&mut self, w: usize, _h: usize) {
- self.last_width = w;
- self.set_editor_prompt();
+ self.el_content_warning.resize(w);
+ self.el_language.resize(w);
}
}