use crate::app::WriteType;
use crate::config::Keybinds;
use crate::input::KeyCode;
use crate::state::WriteParams;
use crate::tui::hints::{self, Hint};
use crate::tui::theme::Theme;
use ratatui::layout::Rect;
use ratatui::text::{Line, Span};
use ratatui::Frame;
const LABEL_WIDTH: usize = 8;
fn label(text: &'static str, theme: &Theme) -> Span<'static> {
Span::styled(
format!(" {text:<width$}", width = LABEL_WIDTH - 1),
theme.dim_style(),
)
}
fn continuation() -> Span<'static> {
Span::raw(" ".repeat(LABEL_WIDTH))
}
fn push_result(lines: &mut Vec<Line<'static>>, theme: &Theme, write: &WriteParams) {
if let Some(result) = &write.result {
lines.push(Line::default());
lines.push(Line::from(vec![
Span::raw(" "),
Span::styled(result.text.clone(), theme.message_style(result.kind)),
]));
}
}
pub(super) fn draw(
frame: &mut Frame,
area: Rect,
theme: &Theme,
kb: &Keybinds,
write: &WriteParams,
custom: Option<String>,
) {
if write.write_type == WriteType::Coil {
draw_coil(frame, area, theme, kb, write, custom);
return;
}
let bits: u16 = match write.write_type {
WriteType::Word => 16,
WriteType::DWord => 32,
WriteType::Coil => 1, };
let raw = write.value.unwrap_or(0) as u32;
let mut bit_spans = vec![label("Bits", theme)];
let mut caret_col = 0usize;
let mut col = 0usize;
for i in (0..bits).rev() {
let set = (raw >> i) & 1 == 1;
let style = if i == write.bit_cursor {
theme.selected_style()
} else if set {
theme.accent_style()
} else {
theme.dim_style()
};
if i == write.bit_cursor {
caret_col = col;
}
bit_spans.push(Span::styled(if set { "1" } else { "0" }, style));
col += 1;
if i % 4 == 0 && i != 0 {
bit_spans.push(Span::raw(" "));
col += 1;
}
}
let footer1 = [
Hint::key(kb.action, "Write"),
Hint::key(kb.exit, "Exit"),
Hint::key(kb.write, "Cycle mode"),
];
let footer2 = [
Hint::key(KeyCode::Char('-'), "Negate"),
Hint::pair(KeyCode::Left, KeyCode::Right, "Bit"),
Hint::key(kb.pause, "Toggle"),
];
let width = hints::min_width(50, &footer1);
let caret = LABEL_WIDTH + caret_col;
let bit_label = format!("bit {}", write.bit_cursor);
let caret_line = if caret + 2 + bit_label.len() <= width as usize - 2 {
Line::from(vec![
Span::raw(" ".repeat(caret)),
Span::styled("\u{25b4}", theme.accent_style()),
Span::styled(format!(" {bit_label}"), theme.dim_style()),
])
} else {
Line::from(vec![
Span::raw(" ".repeat(caret - bit_label.len() - 1)),
Span::styled(bit_label, theme.dim_style()),
Span::raw(" "),
Span::styled("\u{25b4}", theme.accent_style()),
])
};
let value_line = Line::from(vec![
label("Value", theme),
Span::styled(
write.value.map_or_else(String::new, |n| n.to_string()),
theme.base(),
),
super::cursor_span(theme),
]);
let detail = match write.value {
None => Span::styled("(empty)", theme.dim_style()),
Some(n) => {
let (hex, alt) = match write.write_type {
WriteType::Word => {
let r = n as u16;
(
format!("0x{r:04X}"),
alt_view(n, i64::from(r), i64::from(r as i16), 16),
)
}
_ => {
let r = n as u32;
(
format!("0x{r:08X}"),
alt_view(n, i64::from(r), i64::from(r as i32), 32),
)
}
};
let mut text = hex;
if let Some(alt) = alt {
text.push_str(&format!(" \u{b7} {alt}"));
}
Span::styled(text, theme.dim_style())
}
};
let mut lines = vec![
Line::default(),
value_line,
Line::from(vec![continuation(), detail]),
];
if let Some(text) = custom {
lines.push(Line::from(vec![
label("Custom", theme),
Span::styled(text, theme.base()),
]));
}
lines.push(Line::from(bit_spans));
lines.push(caret_line);
let dword = write.write_type == WriteType::DWord;
let func = if write.force_multiple || dword {
"Multiple registers (0x10)"
} else {
"Single register (0x06)"
};
let func_style = if dword {
theme.dim_style()
} else {
theme.base()
};
lines.push(Line::from(vec![
label("Func", theme),
Span::styled(func, func_style),
]));
push_result(&mut lines, theme, write);
super::push_footer(&mut lines, theme, footer1);
lines.push(hints::footer(theme, footer2));
let title = format!("Write [{:?}] @ {}", write.write_type, write.position);
super::render(frame, area, theme, &title, width, lines);
}
fn alt_view(typed: i64, unsigned: i64, signed: i64, bits: u16) -> Option<String> {
if typed < 0 {
Some(format!("u{bits} {unsigned}"))
} else if signed < 0 {
Some(format!("i{bits} {signed}"))
} else {
None
}
}
fn draw_coil(
frame: &mut Frame,
area: Rect,
theme: &Theme,
kb: &Keybinds,
write: &WriteParams,
custom: Option<String>,
) {
let on = write.value.unwrap_or(0) != 0;
let (state, state_style) = if on {
("\u{25c9} ON", theme.ok_style())
} else {
("\u{25cb} OFF", theme.dim_style())
};
let mut lines = vec![
Line::default(),
Line::from(vec![
label("State", theme),
Span::styled(state, state_style),
]),
];
if let Some(text) = custom {
lines.push(Line::from(vec![
label("Custom", theme),
Span::styled(text, theme.base()),
]));
}
push_result(&mut lines, theme, write);
let footer = [
Hint::key(kb.action, "Write"),
Hint::key(kb.pause, "Toggle on/off"),
Hint::key(kb.exit, "Exit"),
];
let width = hints::min_width(44, &footer);
super::push_footer(&mut lines, theme, footer);
let title = format!("Write coil @ {}", write.position);
super::render(frame, area, theme, &title, width, lines);
}