chiark / gitweb /
Changed my mind about using Box<dyn TextFragment>.
authorSimon Tatham <anakin@pobox.com>
Thu, 28 Dec 2023 11:06:15 +0000 (11:06 +0000)
committerSimon Tatham <anakin@pobox.com>
Thu, 28 Dec 2023 11:06:15 +0000 (11:06 +0000)
I think it's going to be a better idea to have each user of
TextFragment separately make an enum that permits precisely the kinds
of TextFragment they want. Firstly, this means we can be statically
sure no rogue weird ones got in there by accident; secondly, it
immediately solves all the problems of finding the ones that need
special handling at render time (extensible-file indicators etc).

And thirdly - this is definitely a thing I'm having trouble getting
used to in Rust - if I _do_ need one particular client of TextFragment
to do everything by means of a list of perfectly generic Box<dyn
TextFragment>, I can choose to do it in _that_ client module, and
text.rs doesn't have to lift a finger to support it!

src/menu.rs
src/text.rs

index ba6ce5ca86206072e53a4ea052d86a04d2063ca6..069ef9eb8676343ee58e91e6532b3dbe7c65c962 100644 (file)
@@ -6,7 +6,7 @@ use super::tui::{
 };
 
 struct Menu {
-    title: Box<dyn TextFragment>,
+    title: FileHeader,
 }
 
 pub fn main_menu() -> Box<dyn ActivityState> {
index 33f834ce15dfdfbdf501f8cc389eeb427e362395..8b3cff0dbf78b1ab98313063091037c3af8bfbf4 100644 (file)
@@ -15,21 +15,13 @@ pub trait TextFragment {
     // Some means of passing a flag to render() that says which username or
     // status is to be highlighted
     fn render(&self, width: usize) -> Vec<ColouredString>;
-
-    fn as_extendable_indicator_mut(&mut self)
-                                   -> Option<&mut ExtendableIndicator> {
-        None
-    }
-    fn as_menu_keypress_mut(&mut self) -> Option<&mut MenuKeypressLine> {
-        None
-    }
 }
 
 pub struct BlankLine {}
 
 impl BlankLine {
-    pub fn new() -> Box<dyn TextFragment> {
-        Box::new(BlankLine{})
+    pub fn new() -> Self {
+        BlankLine{}
     }
 }
 
@@ -59,12 +51,12 @@ impl SeparatorLine {
         timestamp: Option<DateTime<Utc>>,
         favourited: bool,
         boosted: bool,
-        ) -> Box<dyn TextFragment> {
-        Box::new(SeparatorLine {
-                timestamp,
-                favourited,
-                boosted,
-            })
+        ) -> Self {
+        SeparatorLine {
+            timestamp,
+            favourited,
+            boosted,
+        }
     }
 }
 
@@ -119,8 +111,8 @@ fn test_separator() {
 pub struct EditorHeaderSeparator {}
 
 impl EditorHeaderSeparator {
-    pub fn new() -> Box<dyn TextFragment> {
-        Box::new(EditorHeaderSeparator{})
+    pub fn new() -> Self {
+        EditorHeaderSeparator{}
     }
 }
 
@@ -153,22 +145,22 @@ pub struct UsernameHeader {
 }
 
 impl UsernameHeader {
-    pub fn from(account: &str, nameline: &str) -> Box<dyn TextFragment> {
-    Box::new(UsernameHeader{
+    pub fn from(account: &str, nameline: &str) -> Self {
+        UsernameHeader{
             header: "From".to_owned(),
             colour: 'F',
             account: account.to_owned(),
             nameline: nameline.to_owned(),
-        })
+        }
     }
 
-    pub fn via(account: &str, nameline: &str) -> Box<dyn TextFragment> {
-        Box::new(UsernameHeader{
-                header: "Via".to_owned(),
-                colour: 'f',
-                account: account.to_owned(),
-                nameline: nameline.to_owned(),
-            })
+    pub fn via(account: &str, nameline: &str) -> Self {
+        UsernameHeader{
+            header: "Via".to_owned(),
+            colour: 'f',
+            account: account.to_owned(),
+            nameline: nameline.to_owned(),
+        }
     }
 }
 
@@ -291,8 +283,6 @@ impl Paragraph {
         self.words.splice(start..first_non_mention, vec!{});
     }
 
-    pub fn into_box(self) -> Box<dyn TextFragment> { Box::new(self) }
-
     pub fn is_empty(&self) -> bool {
         match self.words.first() {
             None => true,
@@ -419,7 +409,7 @@ impl TextFragment for Paragraph {
 #[test]
 fn test_para_wrap() {
     let p = Paragraph::new().add(&ColouredString::plain(
-            "the quick brown fox  jumps over  the lazy dog")).into_box();
+            "the quick brown fox  jumps over  the lazy dog"));
     assert_eq!(p.render(15), vec! {
             ColouredString::plain("the quick brown"),
             ColouredString::plain("fox  jumps over"),
@@ -427,7 +417,7 @@ fn test_para_wrap() {
         });
 
     let p = Paragraph::new().add(&ColouredString::plain(
-            "  one supercalifragilisticexpialidocious word")).into_box();
+            "  one supercalifragilisticexpialidocious word"));
     assert_eq!(p.render(15), vec! {
             ColouredString::plain("  one"),
             ColouredString::plain("supercalifragilisticexpialidocious"),
@@ -435,7 +425,7 @@ fn test_para_wrap() {
         });
 
     let p = Paragraph::new().add(&ColouredString::plain(
-            "  supercalifragilisticexpialidocious word")).into_box();
+            "  supercalifragilisticexpialidocious word"));
     assert_eq!(p.render(15), vec! {
             ColouredString::plain("  supercalifragilisticexpialidocious"),
             ColouredString::plain("word"),
@@ -443,14 +433,14 @@ fn test_para_wrap() {
 
     let p = Paragraph::new().add(&ColouredString::plain(
             "the quick brown fox  jumps over  the lazy dog"))
-        .set_wrap(false).into_box();
+        .set_wrap(false);
     assert_eq!(p.render(15), vec! {
             ColouredString::plain("the quick brown fox  jumps over  the lazy dog"),
         });
 
     let p = Paragraph::new().add(&ColouredString::plain(
             "the quick brown fox jumps over the lazy dog"))
-        .set_indent(4, 2).into_box();
+        .set_indent(4, 2);
     assert_eq!(p.render(15), vec! {
             ColouredString::plain("    the quick"),
             ColouredString::plain("  brown fox"),
@@ -464,10 +454,10 @@ pub struct FileHeader {
 }
 
 impl FileHeader {
-    pub fn new(text: ColouredString) -> Box<dyn TextFragment> {
-        Box::new(FileHeader{
-                text: text,
-            })
+    pub fn new(text: ColouredString) -> Self {
+        FileHeader{
+            text: text,
+        }
     }
 }
 
@@ -777,10 +767,10 @@ pub struct ExtendableIndicator {
 }
 
 impl ExtendableIndicator {
-    pub fn new() -> Box<dyn TextFragment> {
-        Box::new(ExtendableIndicator{
-                primed: false
-            })
+    pub fn new() -> Self {
+        ExtendableIndicator{
+            primed: false
+        }
     }
 
     pub fn set_primed(&mut self, primed: bool) { self.primed = primed; }
@@ -808,11 +798,6 @@ impl TextFragment for ExtendableIndicator {
             ColouredString::plain(""),
         }
     }
-
-    fn as_extendable_indicator_mut(&mut self)
-                                   -> Option<&mut ExtendableIndicator> {
-        Some(self)
-    }
 }
 
 #[test]
@@ -827,7 +812,7 @@ fn test_extendable() {
             ColouredString::plain(""),
         });
 
-    ei.as_extendable_indicator_mut().unwrap().set_primed(true);
+    ei.set_primed(true);
 
     assert_eq!(ei.render(40), vec! {
             ColouredString::plain(""),
@@ -843,16 +828,16 @@ pub struct InReplyToLine {
 }
 
 impl InReplyToLine {
-    pub fn new(post: &Vec<Paragraph>) -> Box<dyn TextFragment> {
+    pub fn new(post: &Vec<Paragraph>) -> Self {
         let mut para = Paragraph::new().add(&ColouredString::plain("Re: "));
         let currlen = para.words.len();
         for cpara in post {
             para.push_para(cpara);
         }
         para.delete_mention_words_from(currlen);
-        Box::new(InReplyToLine {
-                para: para
-            })
+        InReplyToLine {
+            para: para
+        }
     }
 }
 
@@ -916,7 +901,7 @@ impl NotificationLog {
     pub fn new(
         timestamp: DateTime<Utc>, account: &str, nameline: &str,
         ntype: NotificationLogType, post: Option<&Vec<Paragraph>>)
-        -> Box<dyn TextFragment> {
+        -> Self {
 
         let mut para = Paragraph::new();
 
@@ -935,11 +920,11 @@ impl NotificationLog {
             para.delete_mention_words_from(currlen);
         }
 
-        Box::new(NotificationLog {
-                timestamp: timestamp,
-                account_desc: format!("{} ({})", nameline, account),
-                para: para,
-            })
+        NotificationLog {
+            timestamp: timestamp,
+            account_desc: format!("{} ({})", nameline, account),
+            para: para,
+        }
     }
 }
 
@@ -1013,10 +998,10 @@ pub struct UserListEntry {
 
 impl UserListEntry {
     pub fn new(account: &str, nameline: &str)
-               -> Box<dyn TextFragment> {
-        Box::new(UserListEntry {
-                account_desc: format!("{} ({})", nameline, account),
-            })
+               -> Self {
+        UserListEntry {
+            account_desc: format!("{} ({})", nameline, account),
+        }
     }
 }
 
@@ -1046,7 +1031,7 @@ pub struct Media {
 
 impl Media {
     pub fn new(url: &str, description: &str)
-               -> Box<dyn TextFragment> {
+               -> Self {
         let mut paras = description
             .split('\n')
             .map(|x| Paragraph::new()
@@ -1054,10 +1039,10 @@ impl Media {
                  .add(&ColouredString::uniform(x, 'm')))
             .collect();
         trim_para_list(&mut paras);
-        Box::new(Media {
-                url: url.to_owned(),
-                description: paras,
-            })
+        Media {
+            url: url.to_owned(),
+            description: paras,
+        }
     }
 }
 
@@ -1122,7 +1107,7 @@ pub struct FileStatusLine {
     message: Option<String>,
 }
 
-struct FileStatusLineFinal {
+pub struct FileStatusLineFinal {
     fs: FileStatusLine,
     priwidth: Vec<(usize, usize)>, // min priority, total width of those keys
 }
@@ -1157,7 +1142,7 @@ impl FileStatusLine {
         self
     }
 
-    fn finalise(self) -> FileStatusLineFinal {
+    pub fn finalise(self) -> FileStatusLineFinal {
         let mut bypri = BTreeMap::new();
         for (kp, pri) in &self.keypresses {
             // [key]:desc
@@ -1191,10 +1176,6 @@ impl FileStatusLine {
             priwidth: priwidth
         }
     }
-
-    pub fn into_box(self) -> Box<dyn TextFragment> {
-        Box::new(self.finalise())
-    }
 }
 
 #[test]
@@ -1322,7 +1303,7 @@ fn test_filestatus_render() {
         .add(OurKey::Pr('b'), "Badger", 10)
         .add(OurKey::Pr('c'), "Critical", 1000)
         .add(OurKey::Pr('d'), "Dull", 1)
-        .into_box();
+        .finalise();
 
     assert_eq!(fs.render(80), vec! {
             ColouredString::general(
@@ -1367,7 +1348,7 @@ fn test_filestatus_render() {
     let fs = FileStatusLine::new()
         .set_proportion(1, 11)
         .add(OurKey::Pr('K'), "Keypress", 10)
-        .into_box();
+        .finalise();
 
     assert_eq!(fs.render(19), vec! {
             ColouredString::general(
@@ -1384,7 +1365,7 @@ fn test_filestatus_render() {
     let fs = FileStatusLine::new()
         .message("weasel")
         .add(OurKey::Pr('K'), "Keypress", 10)
-        .into_box();
+        .finalise();
 
     assert_eq!(fs.render(21), vec! {
             ColouredString::general(
@@ -1408,20 +1389,20 @@ pub struct MenuKeypressLine {
 
 impl MenuKeypressLine {
     pub fn new(key: OurKey, description: ColouredString)
-               -> Box<dyn TextFragment> {
+               -> Self {
         // +2 for [] around the key name
         let lwid = UnicodeWidthStr::width(&key_to_string(key) as &str) + 2;
 
         let rwid = description.width();
-        Box::new(MenuKeypressLine {
-                keypress: Keypress {
-                    key: key,
-                    description: description,
-                },
-                lwid: lwid,
-                lmaxwid: lwid,
-                rmaxwid: rwid,
-            })
+        MenuKeypressLine {
+            keypress: Keypress {
+                key: key,
+                description: description,
+            },
+            lwid: lwid,
+            lmaxwid: lwid,
+            rmaxwid: rwid,
+        }
     }
 
     pub fn get_widths(&self) -> (usize, usize) { (self.lmaxwid, self.rmaxwid) }
@@ -1454,10 +1435,6 @@ impl TextFragment for MenuKeypressLine {
             line.truncate(width).to_owned()
         }
     }
-
-    fn as_menu_keypress_mut(&mut self) -> Option<&mut MenuKeypressLine> {
-        Some(self)
-    }
 }
 
 #[test]
@@ -1478,7 +1455,7 @@ fn test_menu_keypress() {
                 "    k    K                 "),
             });
 
-    mk.as_menu_keypress_mut().unwrap().ensure_widths(5, 0);
+    mk.ensure_widths(5, 0);
 
     assert_eq!(mk.render(40), vec! {
             ColouredString::general(