From: Simon Tatham Date: Mon, 8 Jan 2024 19:39:58 +0000 (+0000) Subject: Wrap poll_options into a general display-style trait. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;h=cbd3d41466935c0145ea47b7f1aa4cd239879260;p=mastodonochrome.git Wrap poll_options into a general display-style trait. --- diff --git a/TODO.md b/TODO.md index da2b3e0..92386e6 100644 --- a/TODO.md +++ b/TODO.md @@ -46,11 +46,6 @@ Currently we display sensitive-content tags, but then display the rest of the post anyway. It would be better to (at least have the option to) hide the post and then be able to show it on demand. -This will probably involve some kind of state object in File which -maps post IDs to data about them that informs display. Once we have -that, it could also be used to make the handling of unsubmitted poll -results less horrible. - ### Scrolling to keep the selection in view Various keyboard commands let you select a user or a post from a file diff --git a/src/file.rs b/src/file.rs index bfffdba..1ddf913 100644 --- a/src/file.rs +++ b/src/file.rs @@ -312,6 +312,23 @@ enum UIMode { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SearchDirection { Up, Down } +#[derive(Default)] +struct FileDisplayStyles { + selected_poll_id: Option, + selected_poll_options: HashSet, +} +impl DisplayStyleGetter for FileDisplayStyles { + fn poll_options(&self, id: &str) -> Option> { + self.selected_poll_id.as_ref().and_then( + |s| if s == id { + Some(self.selected_poll_options.clone()) + } else { + None + } + ) + } +} + struct File { contents: FileContents, rendered: HashMap>, @@ -321,7 +338,7 @@ struct File { selection: Option<(isize, usize)>, selection_restrict_to_item: Option, select_aux: Option, // distinguishes fave from unfave, etc - poll_options_selected: HashSet, + display_styles: FileDisplayStyles, file_desc: Type, search_direction: Option, last_search: Option, @@ -394,7 +411,7 @@ impl File { selection: None, selection_restrict_to_item: None, select_aux: None, - poll_options_selected: HashSet::new(), + display_styles: FileDisplayStyles::default(), file_desc, search_direction: None, last_search: None, @@ -421,15 +438,8 @@ impl File { _ => None, }; - let poll_options = - if self.selection_restrict_to_item == Some(index) { - Some(&self.poll_options_selected) - } else { - None - }; - for line in self.contents.get(index) - .render_highlighted(w, highlight, poll_options) + .render_highlighted(w, highlight, &self.display_styles) { for frag in line.split(w) { lines.push(frag.into()); @@ -718,6 +728,12 @@ impl File { LogicalAction::Beep }; + self.display_styles.selected_poll_id = match self.ui_mode { + UIMode::Select(HighlightType::PollOption, _) => + self.selected_id(self.selection), + _ => None, + }; + self.select_aux = match self.ui_mode { UIMode::Select(_, SelectionPurpose::Favourite) => { match self.selected_id(self.selection) { @@ -791,16 +807,15 @@ impl File { "we should only call this if we have a selection"); self.selection_restrict_to_item = Some(item); if self.contents.get(item).is_multiple_choice_poll() { - if self.poll_options_selected.contains(&sub) { - self.poll_options_selected.remove(&sub); + if self.display_styles.selected_poll_options.contains(&sub) { + self.display_styles.selected_poll_options.remove(&sub); } else { - self.poll_options_selected.insert(sub); + self.display_styles.selected_poll_options.insert(sub); } } else { - self.poll_options_selected.clear(); - self.poll_options_selected.insert(sub); + self.display_styles.selected_poll_options.clear(); + self.display_styles.selected_poll_options.insert(sub); } - dbg!(&self.poll_options_selected); self.rerender_selected_item(); self.ensure_enough_rendered(); LogicalAction::Nothing @@ -812,7 +827,8 @@ impl File { self.selection = None; self.ensure_enough_rendered(); } - self.poll_options_selected.clear(); + self.display_styles.selected_poll_id = None; + self.display_styles.selected_poll_options.clear(); self.ui_mode = UIMode::Normal; } @@ -884,7 +900,7 @@ impl File { UtilityActivity::ThreadFile(id, alt).into()), SelectionPurpose::Vote => { match client.vote_in_poll( - &id, self.poll_options_selected.iter().copied()) { + &id, self.display_styles.selected_poll_options.iter().copied()) { Ok(_) => { self.contents.update_items(client); LogicalAction::Nothing @@ -1136,7 +1152,9 @@ impl // the keypress for selecting something. // Otherwise, prioritise the one for // submitting your answer. - if self.poll_options_selected.is_empty() { + if self.display_styles.selected_poll_options + .is_empty() + { fs.add(Space, verb, 98) .add(Ctrl('V'), "Submit Vote", 97) } else { diff --git a/src/text.rs b/src/text.rs index 0caf31e..4fd9dad 100644 --- a/src/text.rs +++ b/src/text.rs @@ -40,9 +40,19 @@ impl ConsumableHighlight for Option { } } +// Trait that can be passed by the caller of render_highlighted(), +// providing information to modify how things are displayed +pub trait DisplayStyleGetter { + fn poll_options(&self, id: &str) -> Option>; +} +struct DefaultDisplayStyle; +impl DisplayStyleGetter for DefaultDisplayStyle { + fn poll_options(&self, _id: &str) -> Option> { None } +} + pub trait TextFragment { fn render(&self, width: usize) -> Vec { - self.render_highlighted(width, None, None) + self.render_highlighted(width, None, &DefaultDisplayStyle) } fn can_highlight(_htype: HighlightType) -> bool where Self : Sized { @@ -54,29 +64,27 @@ pub trait TextFragment { None } fn render_highlighted(&self, width: usize, highlight: Option, - poll_options: Option<&HashSet>) + style: &dyn DisplayStyleGetter) -> Vec; fn is_multiple_choice_poll(&self) -> bool { false } fn render_highlighted_update( &self, width: usize, highlight: &mut Option, - poll_options: Option<&HashSet>) - -> Vec + style: &dyn DisplayStyleGetter) -> Vec { let (new_highlight, text) = match *highlight { Some(Highlight(htype, index)) => { let count = self.count_highlightables(htype); if index < count { - (None, self.render_highlighted( - width, *highlight, poll_options)) + (None, self.render_highlighted(width, *highlight, style)) } else { (Some(Highlight(htype, index - count)), - self.render_highlighted(width, None, poll_options)) + self.render_highlighted(width, None, style)) } } None => { - (None, self.render_highlighted(width, None, poll_options)) + (None, self.render_highlighted(width, None, style)) } }; *highlight = new_highlight; @@ -115,11 +123,11 @@ impl TextFragment for Option { } } fn render_highlighted(&self, width: usize, highlight: Option, - poll_options: Option<&HashSet>) + style: &dyn DisplayStyleGetter) -> Vec { match self { Some(ref inner) => inner.render_highlighted( - width, highlight, poll_options), + width, highlight, style), None => Vec::new(), } } @@ -140,13 +148,11 @@ impl TextFragment for Vec { self.iter().map(|x| x.count_highlightables(htype)).sum() } fn render_highlighted(&self, width: usize, highlight: Option, - _poll_options: Option<&HashSet>) + style: &dyn DisplayStyleGetter) -> Vec { let mut highlight = highlight; - // Ignore poll_options, because that's only used when we're - // rendering a single item containing the poll in question itertools::concat(self.iter().map( - |x| x.render_highlighted_update(width, &mut highlight, None))) + |x| x.render_highlighted_update(width, &mut highlight, style))) } fn highlighted_id(&self, highlight: Option) -> Option { let mut highlight = highlight; @@ -177,7 +183,7 @@ impl BlankLine { impl TextFragment for BlankLine { fn render_highlighted(&self, _width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { Self::render_static() @@ -217,7 +223,7 @@ fn format_date(date: DateTime) -> String { impl TextFragment for SeparatorLine { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut suffix = ColouredString::plain(""); @@ -272,7 +278,7 @@ impl EditorHeaderSeparator { impl TextFragment for EditorHeaderSeparator { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { vec! { @@ -326,7 +332,7 @@ impl UsernameHeader { impl TextFragment for UsernameHeader { fn render_highlighted(&self, _width: usize, highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let header = ColouredString::plain(&format!("{}: ", self.header)); @@ -556,7 +562,7 @@ fn test_para_build() { impl TextFragment for Paragraph { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut lines = Vec::new(); @@ -663,7 +669,7 @@ impl FileHeader { impl TextFragment for FileHeader { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let elephants = width >= 16; @@ -784,7 +790,7 @@ impl Html { impl TextFragment for Html { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { match self { @@ -880,7 +886,7 @@ impl ExtendableIndicator { impl TextFragment for ExtendableIndicator { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let message = if self.primed { @@ -953,7 +959,7 @@ impl InReplyToLine { impl TextFragment for InReplyToLine { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let rendered_para = self.para.render(width - min(width, 3)); @@ -1002,7 +1008,7 @@ impl VisibilityLine { impl TextFragment for VisibilityLine { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let line = match self.vis { @@ -1082,7 +1088,7 @@ impl NotificationLog { impl TextFragment for NotificationLog { fn render_highlighted(&self, width: usize, highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut full_para = Paragraph::new().set_indent(0, 2); @@ -1195,7 +1201,7 @@ impl UserListEntry { impl TextFragment for UserListEntry { fn render_highlighted(&self, width: usize, highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut para = Paragraph::new().set_indent(0, 2); @@ -1268,7 +1274,7 @@ impl Media { impl TextFragment for Media { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut lines: Vec<_> = ColouredString::uniform(&self.url, 'M') @@ -1461,7 +1467,7 @@ fn test_filestatus_build() { impl TextFragment for FileStatusLineFinal { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut line = ColouredString::plain(""); @@ -1649,7 +1655,7 @@ impl MenuKeypressLine { impl TextFragment for MenuKeypressLine { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let ourwidth = self.lmaxwid + self.rmaxwid + 3; // " = " in the middle @@ -1723,7 +1729,7 @@ impl CentredInfoLine { impl TextFragment for CentredInfoLine { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let twidth = width.saturating_sub(1); @@ -1886,7 +1892,7 @@ fn push_fragment_highlighted(lines: &mut Vec, impl TextFragment for StatusDisplay { fn render_highlighted(&self, width: usize, highlight: Option, - poll_options_selected: Option<&HashSet>) + style: &dyn DisplayStyleGetter) -> Vec { let mut lines = Vec::new(); @@ -1903,9 +1909,9 @@ impl TextFragment for StatusDisplay { push_fragment(&mut lines, self.sep.render(width)); push_fragment(&mut lines, self.from.render_highlighted_update( - width, &mut highlight, None)); + width, &mut highlight, &DefaultDisplayStyle)); push_fragment(&mut lines, self.via.render_highlighted_update( - width, &mut highlight, None)); + width, &mut highlight, &DefaultDisplayStyle)); push_fragment(&mut lines, self.vis.render(width)); push_fragment(&mut lines, self.irt.render(width)); push_fragment(&mut lines, self.blank.render(width)); @@ -1922,10 +1928,11 @@ impl TextFragment for StatusDisplay { } if let Some(poll) = &self.poll { push_fragment_opt_highlight(&mut lines, poll.title.render(width)); + let poll_options_selected = style.poll_options(&poll.id); for (i, (voted, desc)) in poll.options.iter().enumerate() { let highlighting_this_option = highlight.consume(HighlightType::PollOption, 1) == Some(0); - let voted = poll_options_selected.map_or( + let voted = poll_options_selected.as_ref().map_or( *voted, |opts| opts.contains(&i)); let option = Paragraph::new().set_indent(0, 2).add( &ColouredString::uniform( @@ -2157,14 +2164,14 @@ impl DetailedStatusDisplay { impl TextFragment for DetailedStatusDisplay { fn render_highlighted(&self, width: usize, highlight: Option, - poll_options: Option<&HashSet>) + style: &dyn DisplayStyleGetter) -> Vec { let mut lines = Vec::new(); let mut highlight = highlight; push_fragment(&mut lines, self.sd.render_highlighted_update( - width, &mut highlight, poll_options)); + width, &mut highlight, style)); push_fragment(&mut lines, self.sep.render(width)); push_fragment(&mut lines, self.blank.render(width)); @@ -2502,7 +2509,7 @@ impl ExamineUserDisplay { impl TextFragment for ExamineUserDisplay { fn render_highlighted(&self, width: usize, _highlight: Option, - _poll_options: Option<&HashSet>) + _style: &dyn DisplayStyleGetter) -> Vec { let mut lines = Vec::new();