1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
use unicode_width::UnicodeWidthStr;

/** Represents a block of some width an height containing text. */
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Block {
    width: usize,
    lines: Vec<String>,
}

/** Repeat a character a given ammount of times. */
fn repeat(c: char, times: usize) -> String {
    std::iter::repeat(c).take(times).collect::<String>()
}

/** Subract usizes and clamp to positive results. */
fn subtract_or_zero(a: usize, b: usize) -> usize {
    if a > b {
        a - b
    } else {
        0
    }
}

/** Join two blocks vertically, requiring blocks to have same width. */
fn stack_same_width(top: &Block, bottom: &Block) -> Block {
    assert_eq!(top.width(), bottom.width());

    let lines = top
        .lines
        .iter()
        .cloned()
        .chain(bottom.lines.iter().cloned())
        .collect::<Vec<String>>();

    Block {
        width: top.width,
        lines,
    }
}

/** Join two blocks horizontally, requiring blocks to have same height. */
pub fn beside_same_height(left: &Block, right: &Block) -> Block {
    assert_eq!(left.height(), right.height());

    let lines = left
        .lines
        .iter()
        .zip(right.lines.iter())
        .map(|a| a.0.to_string() + a.1)
        .collect::<Vec<String>>();

    Block {
        width: left.width + right.width,
        lines,
    }
}

impl Block {
    /** Create empty block with width and height zero */
    pub fn empty() -> Block {
        Block {
            width: 0,
            lines: vec![],
        }
    }

    /** Create block of given width and height 0. */
    pub fn of_width(width: usize) -> Block {
        Block::empty().pad_right(width)
    }

    /** Create block of given height and width 0. */
    pub fn of_height(height: usize) -> Block {
        Block::empty().pad_to_height_bottom(height)
    }

    /** Create block containing given text. Gets width of the text and height 1. */
    pub fn of_text(text: &str) -> Block {
        let width = UnicodeWidthStr::width(text);
        Block {
            width,
            lines: vec![text.to_string()],
        }
    }

    /** Return height of block. */
    pub fn height(&self) -> usize {
        self.lines.len()
    }

    /** Return width of block. */
    pub fn width(&self) -> usize {
        self.width
    }

    /** Add given text at bottom of block, incementing the height. Width of
    block will be increased if needed for added line to fit.*/
    pub fn add_text(&self, text: &str) -> Block {
        self.stack_left(&Block::of_text(text))
    }

    /** Add given text lines at bottom of block, incrementing the height
    accordingly. Width of block will be increades if needed. */
    pub fn add_multiple_texts(&self, texts: &[String]) -> Block {
        let addition = texts
            .iter()
            .fold(Block::empty(), |acc, text| acc.add_text(text));

        self.stack_left(&addition)
    }

    /** Fill right side of block with given number of the filler character. */
    pub fn fill_right(&self, width: usize, filler: char) -> Block {
        let suffix = repeat(filler, width);

        let lines = self
            .lines
            .iter()
            .map(|line| line.to_string() + &suffix)
            .collect::<Vec<String>>();

        Block {
            width: self.width + width,
            lines,
        }
    }

    /** Fill bottom side of block with given number of the filler character. */
    pub fn fill_bottom(&self, height: usize, filler: char) -> Block {
        let padding = repeat(filler, self.width);

        let mut result = self.clone();
        for _ in 0..height {
            result.lines.push(padding.clone())
        }
        result
    }

    /** Pad right side of block with given number of spaces. */
    pub fn pad_right(&self, width: usize) -> Block {
        self.fill_right(width, ' ')
    }

    /** Pad left side of block with given number of spaces. */
    pub fn pad_left(&self, width: usize) -> Block {
        Block::of_width(width).beside_top(self)
    }

    pub fn pad_top(&self, height: usize) -> Block {
        Block::of_height(height).stack_left(self)
    }

    /** Pad bottom side of block with given number of empty lines. */
    pub fn pad_bottom(&self, height: usize) -> Block {
        self.fill_bottom(height, ' ')
    }

    /** Pad right so given width is reached. Wider block is untouched. */
    pub fn pad_to_width_right(&self, width: usize) -> Block {
        self.pad_right(subtract_or_zero(width, self.width))
    }

    /** Pad left so given width is reached. Wider block is untouched. */
    pub fn pad_to_width_left(&self, width: usize) -> Block {
        self.pad_left(subtract_or_zero(width, self.width))
    }

    /** Pad both sides so given width is reached. Wider block is untouched.
    If padding needs to be uneven, there will be more padding on the
    right side. */
    pub fn pad_to_width_center_right(&self, width: usize) -> Block {
        let padding = subtract_or_zero(width, self.width);
        let padding_left = padding / 2;
        let padding_right = padding - padding_left;
        self.pad_left(padding_left).pad_right(padding_right)
    }

    /** Pad both sides so given width is reached. Wider block is untouched.
    If padding needs to be uneven, there will be more padding on the
    left side. */
    pub fn pad_to_width_center_left(&self, width: usize) -> Block {
        let padding = subtract_or_zero(width, self.width);
        let padding_right = padding / 2;
        let padding_left = padding - padding_right;
        self.pad_left(padding_left).pad_right(padding_right)
    }

    /** Pad top so given height is reached. Higher block is untouched. */
    pub fn pad_to_height_top(&self, height: usize) -> Block {
        self.pad_top(subtract_or_zero(height, self.height()))
    }

    /** Pad bottom so given height is reached. Higher block is untouched. */
    pub fn pad_to_height_bottom(&self, height: usize) -> Block {
        self.pad_bottom(subtract_or_zero(height, self.height()))
    }

    /** Pad both top and bottom so given height is reached. Higher block is
    untouched. If padding needs to be uneven, there will be more padding on the
    top side. */
    pub fn pad_to_height_center_top(&self, height: usize) -> Block {
        let padding = subtract_or_zero(height, self.height());
        let padding_bottom = padding / 2;
        let padding_top = padding - padding_bottom;
        self.pad_bottom(padding_bottom).pad_top(padding_top)
    }

    /** Pad both top and bottom so given height is reached. Higher block is
    untouched. If padding needs to be uneven, there will be more padding on the
    bottom side. */
    pub fn pad_to_height_center_bottom(&self, height: usize) -> Block {
        let padding = subtract_or_zero(height, self.height());
        let padding_top = padding / 2;
        let padding_bottom = padding - padding_top;
        self.pad_bottom(padding_bottom).pad_top(padding_top)
    }

    /** Join two blocks horizontally, self to the left and the given
    block to the right, aligning the top side of the blocks. */
    pub fn beside_top(&self, right: &Block) -> Block {
        beside_same_height(
            &self.pad_to_height_bottom(right.height()),
            &right.pad_to_height_bottom(self.height()),
        )
    }

    /** Join two blocks horizontally, self to the left and the given
    block to the right, aligning the bottom side of the blocks. */
    pub fn beside_bottom(&self, right: &Block) -> Block {
        beside_same_height(
            &self.pad_to_height_top(right.height()),
            &right.pad_to_height_top(self.height()),
        )
    }

    /** Join two blocks horizontally, self to the left and the given
    block to the right, aligning the center of the blocks.
    If padding needs to be uneven, there will be more padding on the
    top side. */
    pub fn beside_center_bottom(&self, right: &Block) -> Block {
        beside_same_height(
            &self.pad_to_height_center_top(right.height()),
            &right.pad_to_height_center_top(self.height()),
        )
    }

    /** Join two blocks horizontally, self to the left and the given
    block to the right, aligning the center of the blocks.
    If padding needs to be uneven, there will be more padding on the
    bottom side. */
    pub fn beside_center_top(&self, right: &Block) -> Block {
        beside_same_height(
            &self.pad_to_height_center_bottom(right.height()),
            &right.pad_to_height_center_bottom(self.height()),
        )
    }

    /** Join two blocks vertically, self on the top and the given
    block on the bottom, aligning the right side of the blocks. */
    pub fn stack_right(&self, bottom: &Block) -> Block {
        stack_same_width(
            &self.pad_to_width_left(bottom.width),
            &bottom.pad_to_width_left(self.width),
        )
    }

    /** Join two blocks vertically, self on the top and the given
    block on the bottom, aligning the left side of the blocks. */
    pub fn stack_left(&self, bottom: &Block) -> Block {
        stack_same_width(
            &self.pad_to_width_right(bottom.width),
            &bottom.pad_to_width_right(self.width),
        )
    }

    /** Join two blocks vertically, self on the top and the given
    block on the bottom, aligning the center of the blocks.
    If padding needs to be uneven, there will be more padding on the
    right side. */
    pub fn stack_center_left(&self, bottom: &Block) -> Block {
        stack_same_width(
            &self.pad_to_width_center_right(bottom.width),
            &bottom.pad_to_width_center_right(self.width),
        )
    }

    /** Join two blocks vertically, self on the top and the given
    block on the bottom, aligning the center of the blocks.
    If padding needs to be uneven, there will be more padding on the
    left side. */
    pub fn stack_center_right(&self, bottom: &Block) -> Block {
        stack_same_width(
            &self.pad_to_width_center_left(bottom.width),
            &bottom.pad_to_width_center_left(self.width),
        )
    }

    /** Overlays self in front of given block. Treats spaces as transparent
    characters. */
    pub fn in_front_of(&self, behind: &Block) -> Block {
        self.in_front_of_with_transparency(behind, ' ')
    }

    /** Overlays self in front of given block, showing content of the block
    behind on the characters defined as transparent. */
    pub fn in_front_of_with_transparency(&self, behind: &Block, transparency: char) -> Block {
        // Making sure the blocks is of same size
        let front = self
            .fill_right(subtract_or_zero(behind.width(), self.width()), transparency)
            .fill_bottom(
                subtract_or_zero(behind.height(), self.height()),
                transparency,
            );

        let back = behind
            .pad_to_width_right(self.width)
            .pad_to_height_bottom(self.height());

        // Zip characters and make sure frontmost is shown if not transparent
        let lines = front
            .lines
            .iter()
            .zip(back.lines.iter())
            .map(|(front_line, back_line)| {
                front_line
                    .chars()
                    .zip(back_line.chars())
                    .map(|(front_char, back_char)| {
                        if front_char == transparency {
                            back_char
                        } else {
                            front_char
                        }
                    })
                    .collect::<String>()
            })
            .collect::<Vec<String>>();

        Block {
            width: front.width,
            lines,
        }
    }

    /** Render a string from a block using '\n' as separator between lines.
    Trims away whitespace on the right side of each line, just to save on final
    string length. */
    pub fn render(&self) -> String {
        self.lines
            .iter()
            .map(|line| line.trim_end())
            .collect::<Vec<_>>()
            .join("\n")
    }
}

impl From<char> for Block {
    fn from(text: char) -> Self {
        Block::of_text(&text.to_string())
    }
}

impl From<&str> for Block {
    fn from(text: &str) -> Self {
        Block::of_text(text)
    }
}

impl From<String> for Block {
    fn from(text: String) -> Self {
        Block::of_text(&text)
    }
}

impl ToString for Block {
    fn to_string(&self) -> String {
        self.render()
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn above() {
        let a = Block::of_text("aaa");
        let b = Block::of_text("b").add_text("b").pad_left(1);

        assert_eq!("aaa", a.render());
        assert_eq!(" b\n b", b.render());

        assert_eq!("aaa\n b\n b", a.stack_left(&b).render());
    }

    #[test]
    fn trim_right_side_of_lines() {
        // Do not trim whitespace at left or middle of line
        let b = Block::of_text(" a a   ")
            // After trimming, these lines has other width than first line
            .add_text("bbbbb  ")
            .add_text("c  ")
            // These two empty lines should be trimmed down to zero lenght
            .pad_bottom(2);

        assert_eq!(" a a\nbbbbb\nc\n\n", b.render());
    }
}