use crate::core::geometry::Rect;
use crate::core::event::{Event, EventType, KB_ALT_F1, KB_BACKSPACE, KB_ENTER, KB_ESC, MB_LEFT_BUTTON};
use crate::core::state::StateFlags;
use crate::core::command::{CM_CANCEL, CommandId};
use crate::terminal::Terminal;
use super::view::View;
use super::window::Window;
use super::help_viewer::HelpViewer;
use super::help_file::HelpFile;
use std::rc::Rc;
use std::cell::RefCell;
struct SharedHelpViewer(Rc<RefCell<HelpViewer>>);
impl View for SharedHelpViewer {
fn bounds(&self) -> Rect {
self.0.borrow().bounds()
}
fn set_bounds(&mut self, bounds: Rect) {
self.0.borrow_mut().set_bounds(bounds);
}
fn draw(&mut self, terminal: &mut Terminal) {
self.0.borrow_mut().draw(terminal);
}
fn handle_event(&mut self, event: &mut Event) {
self.0.borrow_mut().handle_event(event);
}
fn can_focus(&self) -> bool {
self.0.borrow().can_focus()
}
fn state(&self) -> StateFlags {
self.0.borrow().state()
}
fn set_state(&mut self, state: StateFlags) {
self.0.borrow_mut().set_state(state);
}
fn get_palette(&self) -> Option<crate::core::palette::Palette> {
self.0.borrow().get_palette()
}
fn get_owner_type(&self) -> super::view::OwnerType {
self.0.borrow().get_owner_type()
}
fn set_owner_type(&mut self, owner_type: super::view::OwnerType) {
self.0.borrow_mut().set_owner_type(owner_type);
}
}
pub struct HelpWindow {
window: Window,
viewer: Rc<RefCell<HelpViewer>>, help_file: Rc<RefCell<HelpFile>>,
history: Vec<String>,
history_pos: usize,
}
impl HelpWindow {
pub fn new(bounds: Rect, title: &str, help_file: Rc<RefCell<HelpFile>>) -> Self {
let mut window = Window::new_for_help(bounds, title);
let viewer_bounds = Rect::new(1, 1, bounds.width() - 2, bounds.height() - 2);
let viewer = Rc::new(RefCell::new(
HelpViewer::new(viewer_bounds).with_scrollbar()
));
window.add(Box::new(SharedHelpViewer(Rc::clone(&viewer))));
Self {
window,
viewer,
help_file,
history: Vec::new(),
history_pos: 0,
}
}
pub fn show_topic(&mut self, topic_id: &str) -> bool {
let help = self.help_file.borrow();
if let Some(topic) = help.get_topic(topic_id) {
self.viewer.borrow_mut().set_topic(topic);
true
} else {
false
}
}
pub fn show_default_topic(&mut self) {
let help = self.help_file.borrow();
if let Some(topic) = help.get_default_topic() {
self.viewer.borrow_mut().set_topic(topic);
}
}
pub fn current_topic(&self) -> Option<String> {
self.viewer.borrow().current_topic().map(|s| s.to_string())
}
pub fn viewer_rc(&self) -> Rc<RefCell<HelpViewer>> {
Rc::clone(&self.viewer)
}
pub fn help_file(&self) -> &Rc<RefCell<HelpFile>> {
&self.help_file
}
pub fn switch_to_topic(&mut self, topic_id: &str) -> bool {
let help = self.help_file.borrow();
if help.get_topic(topic_id).is_none() {
return false;
}
drop(help);
if self.history_pos < self.history.len() {
self.history.truncate(self.history_pos);
}
if let Some(current) = self.viewer.borrow().current_topic() {
self.history.push(current.to_string());
}
let success = self.show_topic(topic_id);
if success {
self.history_pos = self.history.len();
}
success
}
pub fn go_back(&mut self) -> bool {
if self.history_pos > 0 {
self.history_pos -= 1;
let topic_id = self.history[self.history_pos].clone();
self.show_topic(&topic_id);
true
} else {
false
}
}
pub fn go_forward(&mut self) -> bool {
if self.history_pos < self.history.len() {
let topic_id = self.history[self.history_pos].clone();
self.history_pos += 1;
self.show_topic(&topic_id);
true
} else {
false
}
}
pub fn can_go_back(&self) -> bool {
self.history_pos > 0
}
pub fn can_go_forward(&self) -> bool {
self.history_pos < self.history.len()
}
pub fn make_select_topic(&self) -> Option<String> {
let help = self.help_file.borrow();
let topics = help.get_topic_ids();
if topics.is_empty() {
return None;
}
Some(topics[0].clone())
}
pub fn execute(&mut self, app: &mut crate::app::Application) -> CommandId {
self.window.execute(app)
}
pub fn end_modal(&mut self, command: CommandId) {
self.window.end_modal(command);
}
}
impl View for HelpWindow {
fn bounds(&self) -> Rect {
self.window.bounds()
}
fn set_bounds(&mut self, bounds: Rect) {
self.window.set_bounds(bounds);
let viewer_bounds = Rect::new(
bounds.a.x + 1,
bounds.a.y + 1,
bounds.b.x - 1,
bounds.b.y - 1,
);
self.viewer.borrow_mut().set_bounds(viewer_bounds);
}
fn draw(&mut self, terminal: &mut Terminal) {
self.window.draw(terminal);
}
fn handle_event(&mut self, event: &mut Event) {
match event.what {
EventType::Keyboard => {
match event.key_code {
KB_ESC => {
self.window.end_modal(CM_CANCEL);
event.clear();
return;
}
KB_ENTER => {
let target = self.viewer.borrow().get_selected_target().map(|s| s.to_string());
if let Some(target) = target {
self.switch_to_topic(&target);
event.clear();
return;
}
}
KB_ALT_F1 | KB_BACKSPACE => {
self.go_back();
event.clear();
return;
}
_ => {}
}
}
EventType::MouseDown => {
if event.mouse.double_click && event.mouse.buttons & MB_LEFT_BUTTON != 0 {
let target = self.viewer.borrow().get_selected_target().map(|s| s.to_string());
if let Some(target) = target {
self.switch_to_topic(&target);
event.clear();
return;
}
}
}
_ => {}
}
self.window.handle_event(event);
}
fn can_focus(&self) -> bool {
true
}
fn state(&self) -> StateFlags {
self.window.state()
}
fn set_state(&mut self, state: StateFlags) {
self.window.set_state(state);
self.viewer.borrow_mut().set_state(state);
}
fn get_palette(&self) -> Option<crate::core::palette::Palette> {
self.window.get_palette()
}
fn get_end_state(&self) -> crate::core::command::CommandId {
self.window.get_end_state()
}
fn set_end_state(&mut self, command: crate::core::command::CommandId) {
self.window.set_end_state(command);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
fn create_test_help_file() -> (NamedTempFile, Rc<RefCell<HelpFile>>) {
let mut file = NamedTempFile::new().unwrap();
writeln!(file, "# Test Topic {{#test}}").unwrap();
writeln!(file, "").unwrap();
writeln!(file, "This is test content.").unwrap();
file.flush().unwrap();
let help = HelpFile::new(file.path().to_str().unwrap()).unwrap();
(file, Rc::new(RefCell::new(help)))
}
#[test]
fn test_help_window_creation() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let window = HelpWindow::new(bounds, "Help", help);
assert_eq!(window.bounds(), bounds);
}
#[test]
fn test_show_topic() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let mut window = HelpWindow::new(bounds, "Help", help);
assert!(window.show_topic("test"));
assert_eq!(window.current_topic(), Some("test".to_string()));
}
#[test]
fn test_show_default_topic() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let mut window = HelpWindow::new(bounds, "Help", help);
window.show_default_topic();
assert_eq!(window.current_topic(), Some("test".to_string()));
}
#[test]
fn test_show_nonexistent_topic() {
let (_file, help) = create_test_help_file();
let bounds = Rect::new(10, 5, 70, 20);
let mut window = HelpWindow::new(bounds, "Help", help);
assert!(!window.show_topic("nonexistent"));
}
}
pub struct HelpWindowBuilder {
bounds: Option<Rect>,
title: Option<String>,
help_file: Option<Rc<RefCell<HelpFile>>>,
}
impl HelpWindowBuilder {
pub fn new() -> Self {
Self { bounds: None, title: None, help_file: None }
}
#[must_use]
pub fn bounds(mut self, bounds: Rect) -> Self {
self.bounds = Some(bounds);
self
}
#[must_use]
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
#[must_use]
pub fn help_file(mut self, help_file: Rc<RefCell<HelpFile>>) -> Self {
self.help_file = Some(help_file);
self
}
pub fn build(self) -> HelpWindow {
let bounds = self.bounds.expect("HelpWindow bounds must be set");
let title = self.title.expect("HelpWindow title must be set");
let help_file = self.help_file.expect("HelpWindow help_file must be set");
HelpWindow::new(bounds, &title, help_file)
}
pub fn build_boxed(self) -> Box<HelpWindow> {
Box::new(self.build())
}
}
impl Default for HelpWindowBuilder {
fn default() -> Self {
Self::new()
}
}