1use crate::_private::NonExhaustive;
6use crate::text::HasScreenCursor;
7use crate::util::revert_style;
8use rat_event::{HandleEvent, MouseOnly, Outcome, Regular, ct_event, flow};
9use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
10use rat_reloc::{RelocatableState, relocate_area};
11use rat_scrolled::event::ScrollOutcome;
12use rat_scrolled::{Scroll, ScrollArea, ScrollAreaState, ScrollState, ScrollStyle};
13use ratatui::buffer::Buffer;
14use ratatui::layout::{Alignment, Position, Rect};
15use ratatui::style::Style;
16use ratatui::text::Text;
17use ratatui::widgets::{Block, StatefulWidget, Widget, Wrap};
18use std::cell::RefCell;
19use std::cmp::min;
20use std::mem;
21use std::ops::DerefMut;
22
23#[derive(Debug, Clone, Default)]
28pub struct Paragraph<'a> {
29 style: Style,
30 focus_style: Option<Style>,
31
32 wrap: Option<Wrap>,
33 para: RefCell<ratatui::widgets::Paragraph<'a>>,
34
35 block: Option<Block<'a>>,
36 vscroll: Option<Scroll<'a>>,
37 hscroll: Option<Scroll<'a>>,
38}
39
40#[derive(Debug, Clone)]
41pub struct ParagraphStyle {
42 pub style: Style,
43 pub focus: Option<Style>,
44
45 pub block: Option<Block<'static>>,
46 pub scroll: Option<ScrollStyle>,
47
48 pub non_exhaustive: NonExhaustive,
49}
50
51#[derive(Debug)]
53pub struct ParagraphState {
54 pub area: Rect,
57 pub inner: Rect,
60
61 pub lines: usize,
63
64 pub vscroll: ScrollState,
67 pub hscroll: ScrollState,
70
71 pub focus: FocusFlag,
74
75 pub non_exhaustive: NonExhaustive,
76}
77
78impl Default for ParagraphStyle {
79 fn default() -> Self {
80 Self {
81 style: Default::default(),
82 focus: None,
83 block: None,
84 scroll: None,
85 non_exhaustive: NonExhaustive,
86 }
87 }
88}
89
90impl<'a> Paragraph<'a> {
91 pub fn new<T>(text: T) -> Self
92 where
93 T: Into<Text<'a>>,
94 {
95 Self {
96 para: RefCell::new(ratatui::widgets::Paragraph::new(text)),
97 ..Default::default()
98 }
99 }
100
101 pub fn text(mut self, text: impl Into<Text<'a>>) -> Self {
103 let mut para = ratatui::widgets::Paragraph::new(text);
104 if let Some(wrap) = self.wrap {
105 para = para.wrap(wrap);
106 }
107 self.para = RefCell::new(para);
108 self
109 }
110
111 pub fn block(mut self, block: Block<'a>) -> Self {
113 self.block = Some(block);
114 self.block = self.block.map(|v| v.style(self.style));
115 self
116 }
117
118 pub fn scroll(mut self, scroll: Scroll<'a>) -> Self {
120 self.hscroll = Some(scroll.clone().override_horizontal());
121 self.vscroll = Some(scroll.override_vertical());
122 self
123 }
124
125 pub fn hscroll(mut self, scroll: Scroll<'a>) -> Self {
127 self.hscroll = Some(scroll.override_horizontal());
128 self
129 }
130
131 pub fn vscroll(mut self, scroll: Scroll<'a>) -> Self {
133 self.vscroll = Some(scroll.override_vertical());
134 self
135 }
136
137 pub fn styles(mut self, styles: ParagraphStyle) -> Self {
139 self.style = styles.style;
140 if styles.focus.is_some() {
141 self.focus_style = styles.focus;
142 }
143 if styles.block.is_some() {
144 self.block = styles.block;
145 }
146 if let Some(styles) = styles.scroll {
147 self.hscroll = self.hscroll.map(|v| v.styles(styles.clone()));
148 self.vscroll = self.vscroll.map(|v| v.styles(styles));
149 }
150 self.block = self.block.map(|v| v.style(self.style));
151 self
152 }
153
154 pub fn style(mut self, style: Style) -> Self {
156 self.style = style;
157 self.block = self.block.map(|v| v.style(self.style));
158 self
159 }
160
161 pub fn focus_style(mut self, style: Style) -> Self {
163 self.focus_style = Some(style);
164 self
165 }
166
167 pub fn wrap(mut self, wrap: Wrap) -> Self {
169 self.wrap = Some(wrap);
170
171 let mut para = mem::take(self.para.borrow_mut().deref_mut());
172 para = para.wrap(wrap);
173 self.para = RefCell::new(para);
174
175 self
176 }
177
178 pub fn alignment(mut self, alignment: Alignment) -> Self {
180 let mut para = mem::take(self.para.borrow_mut().deref_mut());
181 para = para.alignment(alignment);
182 self.para = RefCell::new(para);
183
184 self
185 }
186
187 pub fn left_aligned(self) -> Self {
189 self.alignment(Alignment::Left)
190 }
191
192 pub fn centered(self) -> Self {
194 self.alignment(Alignment::Center)
195 }
196
197 pub fn right_aligned(self) -> Self {
199 self.alignment(Alignment::Right)
200 }
201
202 pub fn line_width(&self) -> usize {
204 self.para.borrow().line_width()
205 }
206
207 pub fn line_height(&self, width: u16) -> usize {
209 let sa = ScrollArea::new()
210 .block(self.block.as_ref())
211 .h_scroll(self.hscroll.as_ref())
212 .v_scroll(self.vscroll.as_ref());
213 let padding = sa.padding();
214
215 self.para
216 .borrow()
217 .line_count(width.saturating_sub(padding.left + padding.right))
218 }
219}
220
221impl<'a> StatefulWidget for &Paragraph<'a> {
222 type State = ParagraphState;
223
224 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
225 render_paragraph(self, area, buf, state);
226 }
227}
228
229impl StatefulWidget for Paragraph<'_> {
230 type State = ParagraphState;
231
232 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
233 render_paragraph(&self, area, buf, state);
234 }
235}
236
237fn render_paragraph(
238 widget: &Paragraph<'_>,
239 area: Rect,
240 buf: &mut Buffer,
241 state: &mut ParagraphState,
242) {
243 state.area = area;
244
245 let mut para = mem::take(widget.para.borrow_mut().deref_mut());
247
248 let style = widget.style;
249 let focus_style = if let Some(focus_style) = widget.focus_style {
250 style.patch(focus_style)
251 } else {
252 revert_style(widget.style)
253 };
254
255 let sa = ScrollArea::new()
257 .block(widget.block.as_ref())
258 .h_scroll(widget.hscroll.as_ref())
259 .v_scroll(widget.vscroll.as_ref())
260 .style(style);
261 let tmp_inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
263 let pad_inner = sa.padding();
264
265 state.lines = para.line_count(area.width.saturating_sub(pad_inner.left + pad_inner.right));
266
267 state
268 .vscroll
269 .set_max_offset(state.lines.saturating_sub(tmp_inner.height as usize));
270 state.vscroll.set_page_len(tmp_inner.height as usize);
271 state.hscroll.set_max_offset(if widget.wrap.is_some() {
272 0
273 } else {
274 para.line_width().saturating_sub(tmp_inner.width as usize)
275 });
276 state.hscroll.set_page_len(tmp_inner.width as usize);
277 state.inner = sa.inner(area, Some(&state.hscroll), Some(&state.vscroll));
278
279 sa.render(
280 area,
281 buf,
282 &mut ScrollAreaState::new()
283 .h_scroll(&mut state.hscroll)
284 .v_scroll(&mut state.vscroll),
285 );
286
287 para = para.scroll((state.vscroll.offset() as u16, state.hscroll.offset() as u16));
288 (¶).render(state.inner, buf);
289
290 if state.is_focused() {
291 let mut tag = None;
292 for x in state.inner.left()..state.inner.right() {
293 if let Some(cell) = buf.cell_mut(Position::new(x, state.inner.y)) {
294 if tag.is_none() {
295 if cell.symbol() != " " {
296 tag = Some(true);
297 }
298 } else {
299 if cell.symbol() == " " {
300 tag = Some(false);
301 }
302 }
303 if tag == Some(true) || (x - state.inner.x < 3) {
304 cell.set_style(focus_style);
305 }
306 }
307 }
308
309 let y = min(
310 state.inner.y as usize + state.vscroll.page_len() * 6 / 10,
311 (state.inner.y as usize + state.vscroll.max_offset)
312 .saturating_sub(state.vscroll.offset),
313 );
314
315 if y as u16 >= state.inner.y {
316 buf.set_style(Rect::new(state.inner.x, y as u16, 1, 1), focus_style);
317 }
318 }
319
320 *widget.para.borrow_mut().deref_mut() = para;
321}
322
323impl HasFocus for ParagraphState {
324 fn build(&self, builder: &mut FocusBuilder) {
325 builder.leaf_widget(self);
326 }
327
328 fn focus(&self) -> FocusFlag {
329 self.focus.clone()
330 }
331
332 fn area(&self) -> Rect {
333 self.area
334 }
335}
336
337impl HasScreenCursor for ParagraphState {
338 fn screen_cursor(&self) -> Option<(u16, u16)> {
339 None
340 }
341}
342
343impl RelocatableState for ParagraphState {
344 fn relocate(&mut self, offset: (i16, i16), clip: Rect) {
345 self.area = relocate_area(self.area, offset, clip);
346 self.inner = relocate_area(self.inner, offset, clip);
347 self.hscroll.relocate(offset, clip);
348 self.vscroll.relocate(offset, clip);
349 }
350}
351
352impl Clone for ParagraphState {
353 fn clone(&self) -> Self {
354 Self {
355 area: self.area,
356 inner: self.inner,
357 lines: self.lines,
358 vscroll: self.vscroll.clone(),
359 hscroll: self.hscroll.clone(),
360 focus: self.focus.new_instance(),
361 non_exhaustive: NonExhaustive,
362 }
363 }
364}
365
366impl Default for ParagraphState {
367 fn default() -> Self {
368 Self {
369 area: Default::default(),
370 inner: Default::default(),
371 focus: Default::default(),
372 vscroll: Default::default(),
373 hscroll: Default::default(),
374 non_exhaustive: NonExhaustive,
375 lines: 0,
376 }
377 }
378}
379
380impl ParagraphState {
381 pub fn new() -> Self {
382 Self::default()
383 }
384
385 pub fn named(name: &str) -> Self {
386 let mut z = Self::default();
387 z.focus = z.focus.with_name(name);
388 z
389 }
390
391 pub fn line_offset(&self) -> usize {
393 self.vscroll.offset()
394 }
395
396 pub fn set_line_offset(&mut self, offset: usize) -> bool {
398 self.vscroll.set_offset(offset)
399 }
400
401 pub fn col_offset(&self) -> usize {
403 self.hscroll.offset()
404 }
405
406 pub fn set_col_offset(&mut self, offset: usize) -> bool {
408 self.hscroll.set_offset(offset)
409 }
410
411 pub fn scroll_left(&mut self, n: usize) -> bool {
413 self.hscroll.scroll_left(n)
414 }
415
416 pub fn scroll_right(&mut self, n: usize) -> bool {
418 self.hscroll.scroll_right(n)
419 }
420
421 pub fn scroll_up(&mut self, n: usize) -> bool {
423 self.vscroll.scroll_up(n)
424 }
425
426 pub fn scroll_down(&mut self, n: usize) -> bool {
428 self.vscroll.scroll_down(n)
429 }
430}
431
432impl HandleEvent<crossterm::event::Event, Regular, Outcome> for ParagraphState {
433 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> Outcome {
434 flow!(if self.is_focused() {
435 match event {
436 ct_event!(keycode press Up) => self.scroll_up(1).into(),
437 ct_event!(keycode press Down) => self.scroll_down(1).into(),
438 ct_event!(keycode press PageUp) => {
439 self.scroll_up(self.vscroll.page_len() * 6 / 10).into()
440 }
441 ct_event!(keycode press PageDown) => {
442 self.scroll_down(self.vscroll.page_len() * 6 / 10).into()
443 }
444 ct_event!(keycode press Home) => self.set_line_offset(0).into(),
445 ct_event!(keycode press End) => {
446 self.set_line_offset(self.vscroll.max_offset()).into()
447 }
448
449 ct_event!(keycode press Left) => self.scroll_left(1).into(),
450 ct_event!(keycode press Right) => self.scroll_right(1).into(),
451 ct_event!(keycode press ALT-PageUp) => {
452 self.scroll_left(self.hscroll.page_len() * 6 / 10).into()
453 }
454 ct_event!(keycode press ALT-PageDown) => {
455 self.scroll_right(self.hscroll.page_len() * 6 / 10).into()
456 }
457 ct_event!(keycode press ALT-Home) => self.set_col_offset(0).into(),
458 ct_event!(keycode press ALT-End) => {
459 self.set_col_offset(self.hscroll.max_offset()).into()
460 }
461
462 _ => Outcome::Continue,
463 }
464 } else {
465 Outcome::Continue
466 });
467
468 self.handle(event, MouseOnly)
469 }
470}
471
472impl HandleEvent<crossterm::event::Event, MouseOnly, Outcome> for ParagraphState {
473 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> Outcome {
474 let mut sas = ScrollAreaState::new()
475 .area(self.inner)
476 .h_scroll(&mut self.hscroll)
477 .v_scroll(&mut self.vscroll);
478 match sas.handle(event, MouseOnly) {
479 ScrollOutcome::Up(v) => {
480 if self.scroll_up(v) {
481 Outcome::Changed
482 } else {
483 Outcome::Continue
484 }
485 }
486 ScrollOutcome::Down(v) => {
487 if self.scroll_down(v) {
488 Outcome::Changed
489 } else {
490 Outcome::Continue
491 }
492 }
493 ScrollOutcome::Left(v) => {
494 if self.scroll_left(v) {
495 Outcome::Changed
496 } else {
497 Outcome::Continue
498 }
499 }
500 ScrollOutcome::Right(v) => {
501 if self.scroll_right(v) {
502 Outcome::Changed
503 } else {
504 Outcome::Continue
505 }
506 }
507 ScrollOutcome::VPos(v) => self.set_line_offset(v).into(),
508 ScrollOutcome::HPos(v) => self.set_col_offset(v).into(),
509 r => r.into(),
510 }
511 }
512}
513
514pub fn handle_events(
518 state: &mut ParagraphState,
519 focus: bool,
520 event: &crossterm::event::Event,
521) -> Outcome {
522 state.focus.set(focus);
523 HandleEvent::handle(state, event, Regular)
524}
525
526pub fn handle_mouse_events(state: &mut ParagraphState, event: &crossterm::event::Event) -> Outcome {
528 HandleEvent::handle(state, event, MouseOnly)
529}