1use crate::{
2 app::{
3 kanban::{CardPriority, CardStatus},
4 state::{AppStatus, Focus},
5 App, DateTimeFormat,
6 },
7 constants::FIELD_NOT_SET,
8 ui::{
9 rendering::{
10 common::{render_blank_styled_canvas, render_close_button},
11 popup::ViewCard,
12 utils::{
13 calculate_viewport_corrected_cursor_position, centered_rect_with_percentage,
14 check_if_active_and_get_style, check_if_mouse_is_in_area, get_button_style,
15 },
16 },
17 widgets::SelfViewportCorrection,
18 PopUp, Renderable,
19 },
20 util::{date_format_converter, date_format_finder},
21};
22use chrono::{Local, NaiveDate, NaiveDateTime};
23use ratatui::{
24 layout::{Alignment, Constraint, Direction, Layout},
25 text::{Line, Span},
26 widgets::{Block, BorderType, Borders, List, ListItem, Paragraph},
27 Frame,
28};
29
30impl Renderable for ViewCard {
31 fn render(rect: &mut Frame, app: &mut App, is_active: bool) {
32 let popup_area = centered_rect_with_percentage(90, 90, rect.area());
33 render_blank_styled_canvas(rect, &app.current_theme, popup_area, is_active);
35 let error_style = check_if_active_and_get_style(
36 is_active,
37 app.current_theme.inactive_text_style,
38 app.current_theme.error_text_style,
39 );
40 let general_style = check_if_active_and_get_style(
41 is_active,
42 app.current_theme.inactive_text_style,
43 app.current_theme.general_style,
44 );
45 let keyboard_focus_style = check_if_active_and_get_style(
46 is_active,
47 app.current_theme.inactive_text_style,
48 app.current_theme.keyboard_focus_style,
49 );
50 let list_select_style = check_if_active_and_get_style(
51 is_active,
52 app.current_theme.inactive_text_style,
53 app.current_theme.list_select_style,
54 );
55 let card_tags_style = get_button_style(app, Focus::CardTags, None, is_active, false);
56 let card_comments_style =
57 get_button_style(app, Focus::CardComments, None, is_active, false);
58 let save_changes_style = get_button_style(app, Focus::SubmitButton, None, is_active, false);
59 let name_style = get_button_style(app, Focus::CardName, None, is_active, false);
60 let description_style =
61 get_button_style(app, Focus::CardDescription, None, is_active, false);
62 let card_due_default_style =
63 get_button_style(app, Focus::CardDueDate, None, is_active, false);
64 if app.state.current_board_id.is_none() || app.state.current_card_id.is_none() {
65 let no_board_or_card_selected = Paragraph::new("No board or card selected.")
66 .block(
67 Block::default()
68 .title("Card Info")
69 .borders(Borders::ALL)
70 .border_type(BorderType::Rounded)
71 .style(error_style),
72 )
73 .alignment(Alignment::Center);
74 rect.render_widget(no_board_or_card_selected, popup_area);
75 return;
76 }
77
78 let board = app
79 .boards
80 .get_board_with_id(app.state.current_board_id.unwrap());
81 if board.is_none() {
82 let could_not_find_board = Paragraph::new("Could not find board to view card.")
83 .block(
84 Block::default()
85 .title("Card Info")
86 .borders(Borders::ALL)
87 .border_type(BorderType::Rounded)
88 .style(error_style),
89 )
90 .alignment(Alignment::Center)
91 .wrap(ratatui::widgets::Wrap { trim: true });
92 rect.render_widget(could_not_find_board, popup_area);
93 return;
94 }
95
96 let board = board.unwrap();
97 let card = board
98 .cards
99 .get_card_with_id(app.state.current_card_id.unwrap());
100 if card.is_none() {
101 let could_not_find_card = Paragraph::new("Could not find card to view.")
102 .block(
103 Block::default()
104 .title("Card Info")
105 .borders(Borders::ALL)
106 .border_type(BorderType::Rounded)
107 .style(error_style),
108 )
109 .alignment(Alignment::Center)
110 .wrap(ratatui::widgets::Wrap { trim: true });
111 rect.render_widget(could_not_find_card, popup_area);
112 return;
113 }
114
115 let card_being_edited = app.state.get_card_being_edited();
116 let card = if let Some(card_being_edited) = card_being_edited {
117 card_being_edited.1
118 } else {
119 card.unwrap().to_owned()
120 };
121 if app.widgets.date_time_picker.selected_date_time.is_none()
122 && !card.due_date.is_empty()
123 && card.due_date != FIELD_NOT_SET
124 {
125 if let Ok(current_format) = date_format_finder(card.due_date.trim()) {
126 app.widgets.date_time_picker.selected_date_time =
127 match NaiveDateTime::parse_from_str(
128 card.due_date.trim(),
129 current_format.to_parser_string(),
130 ) {
131 Ok(date_time) => Some(date_time),
132 Err(_) => None,
133 };
134 }
135 }
136 let board_name = board.name.clone();
137 let card_name = card.name.clone();
138
139 let main_block_widget = {
141 Block::default()
142 .title(format!("{} >> Board({})", card_name, board_name))
143 .borders(Borders::ALL)
144 .border_type(BorderType::Rounded)
145 .border_style(general_style)
146 };
147
148 let name_paragraph_block = Block::default()
150 .title("Name")
151 .borders(Borders::ALL)
152 .border_type(BorderType::Rounded)
153 .border_style(name_style);
154
155 app.state
156 .text_buffers
157 .card_name
158 .set_block(name_paragraph_block);
159
160 let description_length = app.state.text_buffers.card_description.get_num_lines();
162 let description_block = Block::default()
163 .title(format!("Description ({} line(s))", description_length))
164 .borders(Borders::ALL)
165 .border_type(BorderType::Rounded)
166 .border_style(description_style);
167
168 if app.config.show_line_numbers {
169 app.state
170 .text_buffers
171 .card_description
172 .set_line_number_style(general_style)
173 } else {
174 app.state.text_buffers.card_description.remove_line_number()
175 }
176 app.state
177 .text_buffers
178 .card_description
179 .set_block(description_block);
180
181 let (card_extra_info_widget, card_extra_info_items_len, card_due_date_width) = {
183 let card_date_created = if date_format_finder(&card.date_created).is_ok() {
184 if let Ok(parsed_date) =
185 date_format_converter(&card.date_created, app.config.date_time_format)
186 {
187 Span::styled(format!("Created: {}", parsed_date), general_style)
188 } else {
189 match NaiveDateTime::parse_from_str(
190 &card.date_created,
191 app.config.date_time_format.to_parser_string(),
192 ) {
193 Ok(parsed_date) => Span::styled(
194 format!(
195 "Created: {}",
196 parsed_date.format(app.config.date_time_format.to_parser_string())
197 ),
198 general_style,
199 ),
200 Err(_) => {
201 Span::styled(format!("Created: {}", card.date_created), general_style)
202 }
203 }
204 }
205 } else {
206 Span::styled(format!("Created: {}", card.date_created), general_style)
207 };
208 let card_date_modified = if date_format_finder(&card.date_modified).is_ok() {
209 if let Ok(parsed_date) =
210 date_format_converter(&card.date_modified, app.config.date_time_format)
211 {
212 Span::styled(format!("Modified: {}", parsed_date), general_style)
213 } else {
214 Span::styled(format!("Modified: {}", card.date_modified), general_style)
215 }
216 } else {
217 match NaiveDateTime::parse_from_str(
218 &card.date_modified,
219 app.config.date_time_format.to_parser_string(),
220 ) {
221 Ok(parsed_date) => Span::styled(
222 format!(
223 "Modified: {}",
224 parsed_date.format(app.config.date_time_format.to_parser_string())
225 ),
226 general_style,
227 ),
228 Err(_) => {
229 Span::styled(format!("Modified: {}", card.date_modified), general_style)
230 }
231 }
232 };
233 let card_date_completed = if date_format_finder(&card.date_completed).is_ok() {
234 if let Ok(parsed_date) =
235 date_format_converter(&card.date_completed, app.config.date_time_format)
236 {
237 Span::styled(format!("Completed: {}", parsed_date), general_style)
238 } else {
239 Span::styled(format!("Completed: {}", card.date_completed), general_style)
240 }
241 } else {
242 match NaiveDateTime::parse_from_str(
243 &card.date_completed,
244 app.config.date_time_format.to_parser_string(),
245 ) {
246 Ok(parsed_date) => Span::styled(
247 format!(
248 "Completed: {}",
249 parsed_date.format(app.config.date_time_format.to_parser_string())
250 ),
251 general_style,
252 ),
253 Err(_) => {
254 Span::styled(format!("Completed: {}", card.date_completed), general_style)
255 }
256 }
257 };
258 let card_priority = format!("Priority: {}", card.priority);
259 let card_status = format!("Status: {}", card.card_status);
260 let parsed_due_date = if date_format_finder(&card.due_date).is_ok() {
261 date_format_converter(&card.due_date, app.config.date_time_format)
262 } else {
263 Ok(FIELD_NOT_SET.to_string())
264 };
265 let card_due_date_styled = if let Ok(parsed_due_date) = parsed_due_date {
266 if app.state.focus == Focus::CardDueDate {
267 Span::styled(format!("Due: {}", parsed_due_date), list_select_style)
268 } else if parsed_due_date == FIELD_NOT_SET || parsed_due_date.is_empty() {
269 Span::styled(format!("Due: {}", parsed_due_date), card_due_default_style)
270 } else {
271 let formatted_date_format = date_format_finder(&parsed_due_date).unwrap();
272 let days_left = match formatted_date_format {
273 DateTimeFormat::DayMonthYear
274 | DateTimeFormat::MonthDayYear
275 | DateTimeFormat::YearMonthDay => {
276 let today = Local::now().date_naive();
277 let string_to_naive_date_format = NaiveDate::parse_from_str(
278 &parsed_due_date,
279 app.config.date_time_format.to_parser_string(),
280 )
281 .unwrap();
282 string_to_naive_date_format
283 .signed_duration_since(today)
284 .num_days()
285 }
286 DateTimeFormat::DayMonthYearTime
287 | DateTimeFormat::MonthDayYearTime
288 | DateTimeFormat::YearMonthDayTime {} => {
289 let today = Local::now().naive_local();
290 let string_to_naive_date_format = NaiveDateTime::parse_from_str(
291 &parsed_due_date,
292 app.config.date_time_format.to_parser_string(),
293 )
294 .unwrap();
295 string_to_naive_date_format
296 .signed_duration_since(today)
297 .num_days()
298 }
299 };
300 if !is_active {
301 Span::styled(
302 format!("Due: {}", parsed_due_date),
303 app.current_theme.inactive_text_style,
304 )
305 } else if days_left <= app.config.warning_delta.into() && days_left >= 0 {
306 Span::styled(
307 format!("Due: {}", parsed_due_date),
308 app.current_theme.card_due_warning_style,
309 )
310 } else if days_left < 0 {
311 Span::styled(
312 format!("Due: {}", parsed_due_date),
313 app.current_theme.card_due_overdue_style,
314 )
315 } else {
316 Span::styled(format!("Due: {}", parsed_due_date), card_due_default_style)
317 }
318 }
319 } else if app.state.focus == Focus::CardDueDate {
320 Span::styled(format!("Due: {}", FIELD_NOT_SET), list_select_style)
321 } else {
322 Span::styled(format!("Due: {}", FIELD_NOT_SET), general_style)
323 };
324 let card_priority_styled = if !is_active {
325 Span::styled(card_priority, app.current_theme.inactive_text_style)
326 } else if app.state.focus == Focus::CardPriority {
327 Span::styled(card_priority, app.current_theme.list_select_style)
328 } else if card.priority == CardPriority::High {
329 Span::styled(card_priority, app.current_theme.card_priority_high_style)
330 } else if card.priority == CardPriority::Medium {
331 Span::styled(card_priority, app.current_theme.card_priority_medium_style)
332 } else if card.priority == CardPriority::Low {
333 Span::styled(card_priority, app.current_theme.card_priority_low_style)
334 } else {
335 Span::styled(card_priority, app.current_theme.general_style)
336 };
337 let card_status_styled = if !is_active {
338 Span::styled(card_status, app.current_theme.inactive_text_style)
339 } else if app.state.focus == Focus::CardStatus {
340 Span::styled(card_status, app.current_theme.list_select_style)
341 } else if card.card_status == CardStatus::Complete {
342 Span::styled(card_status, app.current_theme.card_status_completed_style)
343 } else if card.card_status == CardStatus::Active {
344 Span::styled(card_status, app.current_theme.card_status_active_style)
345 } else if card.card_status == CardStatus::Stale {
346 Span::styled(card_status, app.current_theme.card_status_stale_style)
347 } else {
348 Span::styled(card_status, app.current_theme.general_style)
349 };
350 let card_extra_info_items = vec![
351 ListItem::new(vec![Line::from(card_date_created)]),
352 ListItem::new(vec![Line::from(card_date_modified)]),
353 ListItem::new(vec![Line::from(card_due_date_styled.clone())]),
354 ListItem::new(vec![Line::from(card_date_completed)]),
355 ListItem::new(vec![Line::from(card_priority_styled)]),
356 ListItem::new(vec![Line::from(card_status_styled)]),
357 ];
358 let card_extra_info_items_len = card_extra_info_items.len();
359 let card_extra_info = List::new(card_extra_info_items).block(
360 Block::default()
361 .title("Card Info")
362 .borders(Borders::ALL)
363 .border_type(BorderType::Rounded)
364 .border_style(general_style),
365 );
366 (
367 card_extra_info,
368 card_extra_info_items_len,
369 card_due_date_styled.width(),
370 )
371 };
372
373 let card_tag_lines = {
376 let card_tags = if app.state.focus == Focus::CardTags {
377 let mut tags = vec![];
378 if app
379 .state
380 .app_list_states
381 .card_view_tag_list
382 .selected()
383 .is_none()
384 {
385 for (index, tag) in card.tags.iter().enumerate() {
386 tags.push(Span::styled(
387 format!("{}) {} ", index + 1, tag),
388 general_style,
389 ));
390 }
391 } else {
392 let selected_tag = app
393 .state
394 .app_list_states
395 .card_view_tag_list
396 .selected()
397 .unwrap();
398 for (index, tag) in card.tags.iter().enumerate() {
399 if index == selected_tag {
400 tags.push(Span::styled(
401 format!("{}) {} ", index + 1, tag),
402 keyboard_focus_style,
403 ));
404 } else {
405 tags.push(Span::styled(
406 format!("{}) {} ", index + 1, tag),
407 general_style,
408 ));
409 }
410 }
411 }
412 tags
413 } else {
414 let mut tags = vec![];
415 for (index, tag) in card.tags.iter().enumerate() {
416 tags.push(Span::styled(
417 format!("{}) {} ", index + 1, tag),
418 general_style,
419 ));
420 }
421 tags
422 };
423 let mut card_tag_lines = vec![];
424 let mut card_tags_per_line = vec![];
425 let mut collector = String::new();
426 let mut collector_start = 0;
427 let mut collector_end = 0;
428 for (i, tag) in card.tags.iter().enumerate() {
429 let tag_string = format!("{}) {} ", i + 1, tag);
430 if (collector.len() + tag_string.len()) < (popup_area.width - 2) as usize {
431 collector.push_str(&tag_string);
432 collector_end = i + 1;
433 } else {
434 card_tag_lines.push(Line::from(
435 card_tags[collector_start..collector_end].to_vec(),
436 ));
437 card_tags_per_line.push(collector_end - collector_start);
438 collector = String::new();
439 collector.push_str(&tag_string);
440 collector_start = i;
441 collector_end = i + 1;
442 }
443 }
444 if !collector.is_empty() {
445 card_tag_lines.push(Line::from(
446 card_tags[collector_start..collector_end].to_vec(),
447 ));
448 }
449 card_tag_lines
450 };
451
452 let card_comment_lines = {
454 let card_comments = if app.state.focus == Focus::CardComments {
455 let mut comments = vec![];
456 if app
457 .state
458 .app_list_states
459 .card_view_comment_list
460 .selected()
461 .is_none()
462 {
463 for (index, comment) in card.comments.iter().enumerate() {
464 comments.push(Span::styled(
465 format!("{}) {} ", index + 1, comment),
466 general_style,
467 ));
468 }
469 } else {
470 let selected_comment = app
471 .state
472 .app_list_states
473 .card_view_comment_list
474 .selected()
475 .unwrap();
476 for (index, comment) in card.comments.iter().enumerate() {
477 if index == selected_comment {
478 comments.push(Span::styled(
479 format!("{}) {} ", index + 1, comment),
480 keyboard_focus_style,
481 ));
482 } else {
483 comments.push(Span::styled(
484 format!("{}) {} ", index + 1, comment),
485 general_style,
486 ));
487 }
488 }
489 }
490 comments
491 } else {
492 let mut comments = vec![];
493 for (index, comment) in card.comments.iter().enumerate() {
494 comments.push(Span::styled(
495 format!("{}) {} ", index + 1, comment),
496 general_style,
497 ));
498 }
499 comments
500 };
501 let mut card_comment_lines = vec![];
502 let mut collector = String::new();
503 let mut collector_start = 0;
504 let mut collector_end = 0;
505 for (i, comment) in card.comments.iter().enumerate() {
506 let comment_string = format!("{}) {} ", i + 1, comment);
507 if (collector.len() + comment_string.len()) < (popup_area.width - 2) as usize {
508 collector.push_str(&comment_string);
509 collector_end = i + 1;
510 } else {
511 card_comment_lines.push(Line::from(
512 card_comments[collector_start..collector_end].to_vec(),
513 ));
514 collector = String::new();
515 collector.push_str(&comment_string);
516 collector_start = i;
517 collector_end = i + 1;
518 }
519 }
520 if !collector.is_empty() {
521 card_comment_lines.push(Line::from(
522 card_comments[collector_start..collector_end].to_vec(),
523 ));
524 }
525 card_comment_lines
526 };
527
528 let card_chunks = {
530 let min_box_height: u16 = 2;
531 let border_height: u16 = 2;
532 let max_height: u16 = popup_area.height - border_height;
533 let submit_button_height: u16 = 3;
534 let card_name_box_height: u16 = 3;
535 let card_extra_info_height: u16 = 8;
536 let mut available_height: u16 = if app.state.card_being_edited.is_some() {
537 max_height - card_name_box_height - card_extra_info_height - submit_button_height
538 } else {
539 max_height - card_name_box_height - card_extra_info_height
540 };
541
542 let raw_card_description_height =
543 app.state.text_buffers.card_description.get_num_lines() as u16;
544
545 let raw_tags_height = card_tag_lines.len() as u16;
546 let raw_comments_height = card_comment_lines.len() as u16;
547
548 let mut card_description_height = if app.state.focus == Focus::CardDescription {
549 if available_height
550 .saturating_sub(raw_tags_height + border_height)
551 .saturating_sub(raw_comments_height + border_height)
552 > 0
553 {
554 let calc = available_height
555 - raw_tags_height
556 - raw_comments_height
557 - (border_height * 2);
558 if calc < (raw_card_description_height + border_height) {
559 let diff = (raw_card_description_height + border_height) - calc;
560 if diff < min_box_height {
561 raw_card_description_height + border_height
562 } else {
563 calc
564 }
565 } else {
566 calc
567 }
568 } else if (raw_card_description_height + border_height) <= available_height {
569 raw_card_description_height + border_height
570 } else {
571 available_height
572 }
573 } else if ((raw_card_description_height + border_height) <= available_height)
574 && app.state.focus != Focus::CardTags
575 && app.state.focus != Focus::CardComments
576 {
577 raw_card_description_height.saturating_sub(border_height)
578 } else {
579 min_box_height
580 };
581
582 available_height = available_height.saturating_sub(card_description_height);
583
584 let card_tags_height = if available_height > 0 {
585 if app.state.focus == Focus::CardTags {
586 raw_tags_height + border_height
587 } else {
588 min_box_height
589 }
590 } else {
591 min_box_height
592 };
593
594 available_height = available_height.saturating_sub(card_tags_height);
595
596 let card_comments_height = if available_height > 0 {
597 if app.state.focus == Focus::CardComments {
598 raw_comments_height + border_height
599 } else {
600 min_box_height
601 }
602 } else {
603 min_box_height
604 };
605
606 available_height = available_height.saturating_sub(card_comments_height);
607
608 if available_height > 0 {
609 card_description_height += available_height;
610 }
611
612 if app.state.card_being_edited.is_some() {
613 Layout::default()
614 .direction(Direction::Vertical)
615 .constraints([
616 Constraint::Length(card_name_box_height),
617 Constraint::Length(card_description_height),
618 Constraint::Length(card_extra_info_height),
619 Constraint::Length(card_tags_height),
620 Constraint::Length(card_comments_height),
621 Constraint::Length(submit_button_height),
622 ])
623 .margin(1)
624 .split(popup_area)
625 } else {
626 Layout::default()
627 .direction(Direction::Vertical)
628 .constraints([
629 Constraint::Length(card_name_box_height),
630 Constraint::Length(card_description_height),
631 Constraint::Length(card_extra_info_height),
632 Constraint::Length(card_tags_height),
633 Constraint::Length(card_comments_height),
634 ])
635 .margin(1)
636 .split(popup_area)
637 }
638 };
639
640 if app.state.z_stack.last() == Some(&PopUp::DateTimePicker) {
641 if app.widgets.date_time_picker.get_anchor().is_none() {
642 app.widgets.date_time_picker.set_anchor(Some((
643 card_chunks[2].x + card_due_date_width as u16 + 2,
644 card_chunks[2].y + 3,
645 ))); }
647 app.widgets.date_time_picker.current_viewport = Some(rect.area());
648 }
649
650 if is_active
651 && check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &card_chunks[2])
652 {
653 let top_of_list = card_chunks[2].y + 1;
654 let mut bottom_of_list = card_chunks[2].y + card_extra_info_items_len as u16;
655 if bottom_of_list > card_chunks[2].bottom() {
656 bottom_of_list = card_chunks[2].bottom();
657 }
658 let mouse_y = app.state.current_mouse_coordinates.1;
659 if mouse_y >= top_of_list && mouse_y <= bottom_of_list {
660 match mouse_y - top_of_list {
661 2 => {
662 app.state.set_focus(Focus::CardDueDate);
663 app.state.mouse_focus = Some(Focus::CardDueDate);
664 app.state
665 .app_list_states
666 .card_view_comment_list
667 .select(None);
668 app.state.app_list_states.card_view_tag_list.select(None);
669 }
670 4 => {
671 app.state.set_focus(Focus::CardPriority);
672 app.state.mouse_focus = Some(Focus::CardPriority);
673 app.state
674 .app_list_states
675 .card_view_comment_list
676 .select(None);
677 app.state.app_list_states.card_view_tag_list.select(None);
678 }
679 5 => {
680 app.state.set_focus(Focus::CardStatus);
681 app.state.mouse_focus = Some(Focus::CardStatus);
682 app.state
683 .app_list_states
684 .card_view_comment_list
685 .select(None);
686 app.state.app_list_states.card_view_tag_list.select(None);
687 }
688 _ => {
689 app.state.set_focus(Focus::NoFocus);
690 app.state.mouse_focus = None;
691 }
692 }
693 app.state
694 .app_list_states
695 .card_view_list
696 .select(Some((mouse_y - top_of_list) as usize));
697 } else {
698 app.state.app_list_states.card_view_list.select(None);
699 }
700 };
701 if is_active
702 && check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &card_chunks[0])
703 {
704 app.state.set_focus(Focus::CardName);
705 app.state.mouse_focus = Some(Focus::CardName);
706 app.state
707 .app_list_states
708 .card_view_comment_list
709 .select(None);
710 app.state.app_list_states.card_view_tag_list.select(None);
711 }
712 if is_active
713 && check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &card_chunks[1])
714 {
715 app.state.set_focus(Focus::CardDescription);
716 app.state.mouse_focus = Some(Focus::CardDescription);
717 app.state
718 .app_list_states
719 .card_view_comment_list
720 .select(None);
721 app.state.app_list_states.card_view_tag_list.select(None);
722 }
723
724 let card_tags_widget = Paragraph::new(card_tag_lines.clone())
725 .block(
726 Block::default()
727 .title(format!("Tags ({})", card.tags.len()))
728 .border_type(BorderType::Rounded)
729 .borders(Borders::ALL)
730 .border_style(card_tags_style),
731 )
732 .alignment(Alignment::Left);
733
734 let card_comments_widget = Paragraph::new(card_comment_lines.clone())
735 .block(
736 Block::default()
737 .title(format!("Comments ({})", card.comments.len()))
738 .border_type(BorderType::Rounded)
739 .borders(Borders::ALL)
740 .border_style(card_comments_style),
741 )
742 .alignment(Alignment::Left);
743
744 if is_active
745 && check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &card_chunks[3])
746 {
747 app.state.set_focus(Focus::CardTags);
748 app.state.mouse_focus = Some(Focus::CardTags);
749 app.state
750 .app_list_states
751 .card_view_comment_list
752 .select(None);
753 }
754
755 if is_active
756 && check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &card_chunks[4])
757 {
758 app.state.set_focus(Focus::CardComments);
759 app.state.mouse_focus = Some(Focus::CardComments);
760 app.state.app_list_states.card_view_tag_list.select(None);
761 }
762
763 if app.state.app_status == AppStatus::UserInput {
764 match app.state.focus {
765 Focus::CardName => {
766 let (x_pos, y_pos) = calculate_viewport_corrected_cursor_position(
767 &app.state.text_buffers.card_name,
768 &app.config.show_line_numbers,
769 &card_chunks[0],
770 );
771 rect.set_cursor_position((x_pos, y_pos));
772 }
773 Focus::CardDescription => {
774 let (x_pos, y_pos) = calculate_viewport_corrected_cursor_position(
775 &app.state.text_buffers.card_description,
776 &app.config.show_line_numbers,
777 &card_chunks[1],
778 );
779 rect.set_cursor_position((x_pos, y_pos));
780 }
781 Focus::CardTags => {
782 if app
783 .state
784 .app_list_states
785 .card_view_tag_list
786 .selected()
787 .is_some()
788 && !app.state.text_buffers.card_tags.is_empty()
789 {
790 let selected_index = app
791 .state
792 .app_list_states
793 .card_view_tag_list
794 .selected()
795 .unwrap();
796 let mut counter = 0;
797 let mut y_index = 0;
798 let mut length_before_selected_tag = 0;
799 let mut prv_spans_length = 0;
800 let tag_offset = 3;
801 for line in card_tag_lines.iter() {
802 for _ in line.spans.iter() {
803 if counter == selected_index {
804 break;
805 } else {
806 let element = line.spans.get(counter - prv_spans_length);
807 if let Some(element) = element {
808 length_before_selected_tag += element.content.len();
809 }
810 counter += 1;
811 }
812 }
813 if counter == selected_index {
814 break;
815 }
816 y_index += 1;
817 prv_spans_length += line.spans.iter().len();
818 length_before_selected_tag = 0;
819 }
820 let digits_in_counter = (counter + 1).to_string().len();
821 if let Some(text_box) = app.state.text_buffers.card_tags.get(selected_index)
822 {
823 let text_box_cursor = text_box.cursor();
824 let x_pos = card_chunks[3].left()
825 + length_before_selected_tag as u16
826 + text_box_cursor.1 as u16
827 + tag_offset
828 + digits_in_counter as u16;
829 let y_pos = card_chunks[3].top() + y_index as u16 + 1;
830
831 if app.state.focus == Focus::CardTags {
832 app.widgets.tag_picker.set_anchor(Some((
833 x_pos - (text_box_cursor.1 as u16)
834 + (text_box.get_joined_lines().len() as u16),
835 y_pos,
836 )));
837 }
838
839 rect.set_cursor_position((x_pos, y_pos));
841 }
842 }
843 }
844 Focus::CardComments => {
845 if app
846 .state
847 .app_list_states
848 .card_view_comment_list
849 .selected()
850 .is_some()
851 && !app.state.text_buffers.card_comments.is_empty()
852 {
853 let selected_index = app
854 .state
855 .app_list_states
856 .card_view_comment_list
857 .selected()
858 .unwrap();
859 let mut counter = 0;
860 let mut y_index = 0;
861 let mut length_before_selected_comment = 0;
862 let mut prv_spans_length = 0;
863 let comment_offset = 3;
864 for line in card_comment_lines.iter() {
865 for _ in line.spans.iter() {
866 if counter == selected_index {
867 break;
868 } else {
869 let element = line.spans.get(counter - prv_spans_length);
870 if let Some(element) = element {
871 length_before_selected_comment += element.content.len();
872 }
873 counter += 1;
874 }
875 }
876 if counter == selected_index {
877 break;
878 }
879 y_index += 1;
880 prv_spans_length += line.spans.iter().len();
881 length_before_selected_comment = 0;
882 }
883 let digits_in_counter = (counter + 1).to_string().len();
884 if let Some(text_box) =
885 app.state.text_buffers.card_comments.get(selected_index)
886 {
887 let text_box_cursor = text_box.cursor();
888 let x_pos = card_chunks[4].left()
889 + length_before_selected_comment as u16
890 + text_box_cursor.1 as u16
891 + comment_offset
892 + digits_in_counter as u16;
893 let y_pos = card_chunks[4].top() + y_index as u16 + 1;
894 rect.set_cursor_position((x_pos, y_pos));
895 }
896 }
897 }
898 _ => {}
899 }
900 }
901
902 rect.render_widget(main_block_widget, popup_area);
904 rect.render_widget(app.state.text_buffers.card_name.widget(), card_chunks[0]);
905 rect.render_widget(
906 app.state.text_buffers.card_description.widget(),
907 card_chunks[1],
908 );
909 rect.render_widget(card_extra_info_widget, card_chunks[2]);
910 rect.render_widget(card_tags_widget, card_chunks[3]);
911 rect.render_widget(card_comments_widget, card_chunks[4]);
912
913 if app.state.card_being_edited.is_some() {
915 if is_active
916 && check_if_mouse_is_in_area(&app.state.current_mouse_coordinates, &card_chunks[5])
917 {
918 app.state.set_focus(Focus::SubmitButton);
919 app.state.mouse_focus = Some(Focus::SubmitButton);
920 app.state
921 .app_list_states
922 .card_view_comment_list
923 .select(None);
924 app.state.app_list_states.card_view_tag_list.select(None);
925 }
926 let save_changes_button = Paragraph::new("Save Changes")
927 .block(
928 Block::default()
929 .title("Save Changes")
930 .borders(Borders::ALL)
931 .border_type(BorderType::Rounded)
932 .border_style(save_changes_style),
933 )
934 .alignment(Alignment::Center);
935 rect.render_widget(save_changes_button, card_chunks[5]);
936 }
937
938 if app.config.enable_mouse_support {
939 render_close_button(rect, app, is_active);
940 }
941 }
942}