Skip to content

Commit

Permalink
Message alignment on draw
Browse files Browse the repository at this point in the history
Instead of applying padding to the actual data in the line buffers, this
applies the correct padding on the fly during line height calculation
and drawing.

Not a very good solution since I did this in order to be able to switch
layouts on the fly (osa1#321) but it still doesn't work because we don't always
align every line (i.e topic line, server stuff).
  • Loading branch information
trevarj committed May 8, 2021
1 parent be66a80 commit 9458533
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 54 deletions.
13 changes: 13 additions & 0 deletions crates/libtiny_tui/src/line_split.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::msg_area::Layout;

/// Cache that stores the state of a line's height calculation.
/// `line_count` is used as the dirty bit to invalidate the cache.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -43,6 +45,17 @@ impl LineType {
}
}

impl From<Layout> for LineType {
fn from(layout: Layout) -> Self {
match layout {
Layout::Compact => LineType::Msg,
Layout::Aligned { .. } => LineType::AlignedMsg {
msg_padding: layout.msg_padding(),
},
}
}
}

impl LineDataCache {
pub(crate) fn input_line(width: i32, nick_length: usize) -> LineDataCache {
LineDataCache {
Expand Down
39 changes: 3 additions & 36 deletions crates/libtiny_tui/src/messaging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ pub(crate) struct Timestamp {
min: i32,
}

// 80 characters. TODO: We need to make sure we don't need more whitespace than that. We should
// probably add an upper bound to max_nick_length config field?
static WHITESPACE: &str =
" ";

impl Timestamp {
/// The width of the timestamp plus a space
pub(crate) const WIDTH: usize = 6;
Expand All @@ -63,11 +58,6 @@ impl Timestamp {
SegStyle::Timestamp,
);
}

/// Inserts spaces for a timestamp slot. Used in aligned layout.
fn blank(msg_area: &mut MsgArea) {
msg_area.add_text(&WHITESPACE[..Timestamp::WIDTH], SegStyle::Timestamp);
}
}

impl From<Tm> for Timestamp {
Expand Down Expand Up @@ -256,8 +246,6 @@ impl MessagingUI {
if let Some(ts_) = self.last_activity_ts {
if ts_ != ts {
ts.stamp(&mut self.msg_area);
} else if matches!(self.msg_area.layout(), Layout::Aligned { .. }) {
Timestamp::blank(&mut self.msg_area)
}
} else {
ts.stamp(&mut self.msg_area);
Expand Down Expand Up @@ -316,30 +304,15 @@ impl MessagingUI {
let nick_color = self.get_nick_color(sender);
let nick_col_style = SegStyle::NickColor(nick_color);

// actions are /me msgs so they don't show the nick in the nick column, but in the msg
let layout = self.msg_area.layout();
let format_nick = |s: &str| -> String {
if let Layout::Aligned { max_nick_len, .. } = layout {
let mut aligned = format!("{:>padding$.padding$}", s, padding = max_nick_len);
if s.len() > max_nick_len {
aligned.pop();
aligned.push('…');
}
aligned
} else {
s.to_string()
}
};
if is_action {
self.msg_area
.add_text(&format_nick("**"), SegStyle::UserMsg);
self.msg_area.add_text("**", SegStyle::UserAction);
// separator between nick and msg
self.msg_area.add_text(" ", SegStyle::Faded);
self.msg_area.add_text(sender, nick_col_style);
// a space replacing the usual ':'
self.msg_area.add_text(" ", SegStyle::UserMsg);
} else {
self.msg_area.add_text(&format_nick(sender), nick_col_style);
self.msg_area.add_text(sender, nick_col_style);
// separator between nick and msg
self.msg_area.add_text(": ", SegStyle::Faded);
}
Expand Down Expand Up @@ -443,7 +416,7 @@ impl MessagingUI {
let line_idx = self.get_activity_line_idx(ts);
self.msg_area.modify_line(line_idx, |line| {
line.add_text(old_nick, SegStyle::Faded);
line.add_char('>', SegStyle::Nick);
line.add_char('>', SegStyle::NickChange);
line.add_text(new_nick, SegStyle::Faded);
});
}
Expand All @@ -467,12 +440,6 @@ impl MessagingUI {
}
_ => {
self.add_timestamp(ts);
if let Layout::Aligned { max_nick_len, .. } = self.msg_area.layout() {
self.msg_area.add_text(
&WHITESPACE[..max_nick_len + MSG_NICK_SUFFIX_LEN],
SegStyle::UserMsg,
)
}
self.msg_area.set_current_line_alignment();
let line_idx = self.msg_area.flush_line();
self.last_activity_line = Some(ActivityLine { ts, line_idx });
Expand Down
82 changes: 72 additions & 10 deletions crates/libtiny_tui/src/msg_area/line.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::line_split::LineType;
use crate::messaging::{Timestamp, MSG_NICK_SUFFIX_LEN};
use crate::{
config::{Colors, Style},
line_split::LineDataCache,
utils::translate_irc_control_chars,
};

use std::mem;
use termbox_simple::{self, Termbox};

Expand All @@ -19,7 +21,7 @@ pub(crate) struct Line {
line_data: LineDataCache,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
struct StyledString {
string: String,
style: SegStyle,
Expand All @@ -37,11 +39,12 @@ pub(crate) enum SegStyle {

/// A style from the current color scheme.
UserMsg,
UserAction,
ErrMsg,
Topic,
Join,
Part,
Nick,
NickChange,
Faded,
Highlight,
Timestamp,
Expand All @@ -56,12 +59,12 @@ impl StyledString {
fg: u16::from(colors.nick[idx % colors.nick.len()]),
bg: colors.user_msg.bg,
},
UserMsg => colors.user_msg,
UserMsg | UserAction => colors.user_msg,
ErrMsg => colors.err_msg,
Topic => colors.topic,
Join => colors.join,
Part => colors.part,
Nick => colors.nick_change,
NickChange => colors.nick_change,
Faded => colors.faded,
Highlight => colors.highlight,
Timestamp => colors.timestamp,
Expand Down Expand Up @@ -165,11 +168,15 @@ impl Line {
let msg_padding = self.line_type().msg_padding();
if self.line_data.is_dirty() || self.line_data.needs_resize(width, 0, msg_padding) {
self.line_data = LineDataCache::msg_line(width, msg_padding);
let mut full_line = self
.segments

let padsegs = self.padding();
let skip = padsegs.len();

let mut full_line = padsegs
.iter()
.flat_map(|s| s.string.chars())
.chain(self.current_seg.string.chars());
.chain(self.segments.iter().skip(skip))
.chain(std::iter::once(&self.current_seg))
.flat_map(|s| s.string.chars());
self.line_data.calculate_height(&mut full_line, 0);
}
self.line_data.get_line_count().unwrap() as i32
Expand All @@ -189,9 +196,12 @@ impl Line {
let mut char_idx = 0;
let mut split_indices_iter = self.line_data.get_splits().iter().copied().peekable();

for seg in self
.segments
let padsegs = self.padding();
let skip = padsegs.len();

for seg in padsegs
.iter()
.chain(self.segments.iter().skip(skip))
.chain(std::iter::once(&self.current_seg))
{
let sty = seg.style(colors);
Expand Down Expand Up @@ -226,6 +236,58 @@ impl Line {
}
}
}

/// Checks if padding is needed to align first few segments.
/// Returns Vec of modified segments or extra padding segments
fn padding(&self) -> Vec<StyledString> {
if let Some(msg_padding) = self.line_type().msg_padding() {
match self.segments.get(..2) {
Some([first, second]) => {
match (first.style, second.style) {
(SegStyle::Timestamp, SegStyle::NickColor(_))
| (SegStyle::Timestamp, SegStyle::UserAction) => {
// timestamp followed by nick/me
let padded_nick = align_seg(second, msg_padding);
return vec![first.clone(), padded_nick];
}
(SegStyle::NickColor(_), _) | (SegStyle::UserAction, _) => {
// no timestamp, nick/me followed by message
let mut blank_ts = StyledString::default();
blank_ts.string = " ".repeat(6);
let padded_nick = align_seg(first, msg_padding);
return vec![blank_ts, padded_nick, second.clone()];
}
_ => {}
}
}
_ => {
match self.segments.get(0) {
Some(first) => {
// full padding needed on things like Join or Nickchange
let mut pad = StyledString::default();
pad.string = " ".repeat(msg_padding);
return vec![pad, first.clone()];
}
None => {}
}
}
}
}
// padding ignored
vec![]
}
}

fn align_seg(seg: &StyledString, padding: usize) -> StyledString {
let padding = padding - Timestamp::WIDTH - MSG_NICK_SUFFIX_LEN;
let mut s = seg.clone();
let mut aligned = format!("{:>padding$.padding$}", s.string, padding = padding);
if s.string.len() > padding {
aligned.pop();
aligned.push('…');
}
s.string = aligned;
s
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
10 changes: 2 additions & 8 deletions crates/libtiny_tui/src/msg_area/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use termbox_simple::Termbox;

pub(crate) use self::line::{Line, SegStyle};
use crate::config::Colors;
use crate::line_split::LineType;
use crate::messaging::{Timestamp, MSG_NICK_SUFFIX_LEN};

pub(crate) struct MsgArea {
Expand Down Expand Up @@ -38,7 +37,7 @@ pub(crate) enum Layout {
}

impl Layout {
fn msg_padding(&self) -> usize {
pub(crate) fn msg_padding(&self) -> usize {
match self {
Layout::Compact => 0,
Layout::Aligned { max_nick_len } => {
Expand Down Expand Up @@ -72,14 +71,9 @@ impl MsgArea {
self.lines_height = None;
}

pub(crate) fn layout(&self) -> Layout {
self.layout
}

/// Used to force a line to be aligned.
pub(crate) fn set_current_line_alignment(&mut self) {
let msg_padding = self.layout.msg_padding();
self.line_buf.set_type(LineType::AlignedMsg { msg_padding });
self.line_buf.set_type(self.layout.into());
}

pub(crate) fn draw(&mut self, tb: &mut Termbox, colors: &Colors, pos_x: i32, pos_y: i32) {
Expand Down
13 changes: 13 additions & 0 deletions crates/libtiny_tui/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,19 @@ fn test_alignment_long_string() {
|mentions irc.server_1.org #chan |";

expect_screen(screen, &tui.get_front_buffer(), 40, 5, Location::caller());

tui.add_privmsg("veryverylongnickname", "hi", ts, &target, false, false);
tui.draw();

#[rustfmt::skip]
let screen =
"|00:00 osa1: 12345678901234567890|
| 1234567890 |
| veryverylon…: hi |
|osa1: |
|mentions irc.server_1.org #chan |";

expect_screen(screen, &tui.get_front_buffer(), 40, 5, Location::caller());
}
#[test]
fn test_resize() {
Expand Down

0 comments on commit 9458533

Please sign in to comment.