use crate::_private::NonExhaustive;
use crate::button::{Button, ButtonState, ButtonStyle};
use crate::event::{
ButtonOutcome, ConsumedEvent, Dialog, HandleEvent, Outcome, Regular, ct_event, flow,
};
use crate::focus::{FocusBuilder, FocusFlag, HasFocus};
use crate::layout::{DialogItem, LayoutOuter};
use crate::text::HasScreenCursor;
use crate::util::{block_padding2, reset_buf_area};
use rat_event::MouseOnly;
use rat_reloc::RelocatableState;
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Constraint, Flex, Position, Rect, Size};
use ratatui_core::style::Style;
use ratatui_core::widgets::{StatefulWidget, Widget};
use ratatui_crossterm::crossterm::event::Event;
use ratatui_widgets::block::Block;
use ratatui_widgets::borders::BorderType;
#[derive(Debug, Clone, Default)]
pub struct DialogFrame<'a> {
block: Block<'a>,
button_style: ButtonStyle,
layout: LayoutOuter,
ok_text: &'a str,
no_cancel: bool,
cancel_text: &'a str,
}
#[derive(Debug, Clone)]
pub struct DialogFrameStyle {
pub style: Style,
pub block: Option<Block<'static>>,
pub border_style: Option<Style>,
pub title_style: Option<Style>,
pub button_style: Option<ButtonStyle>,
pub layout: Option<LayoutOuter>,
pub ok_text: Option<&'static str>,
pub no_cancel: Option<bool>,
pub cancel_text: Option<&'static str>,
pub non_exhaustive: NonExhaustive,
}
impl Default for DialogFrameStyle {
fn default() -> Self {
Self {
style: Default::default(),
block: Default::default(),
border_style: Default::default(),
title_style: Default::default(),
button_style: Default::default(),
layout: Default::default(),
ok_text: Default::default(),
no_cancel: Default::default(),
cancel_text: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
#[derive(Debug, Clone)]
pub struct DialogFrameState {
pub area: Rect,
pub widget_area: Rect,
pub ok: ButtonState,
pub no_cancel: bool,
pub cancel: ButtonState,
pub non_exhaustive: NonExhaustive,
}
impl<'a> DialogFrame<'a> {
pub fn new() -> Self {
Self {
block: Block::bordered().border_type(BorderType::Plain),
button_style: Default::default(),
layout: LayoutOuter::new()
.left(Constraint::Percentage(19))
.top(Constraint::Length(3))
.right(Constraint::Percentage(19))
.bottom(Constraint::Length(3)),
ok_text: "Ok",
no_cancel: false,
cancel_text: "Cancel",
}
}
pub fn styles(mut self, styles: DialogFrameStyle) -> Self {
if let Some(block) = styles.block {
self.block = block;
}
self.block = self.block.style(styles.style);
if let Some(border_style) = styles.border_style {
self.block = self.block.border_style(border_style);
}
if let Some(title_style) = styles.title_style {
self.block = self.block.title_style(title_style);
}
if let Some(button_style) = styles.button_style {
self.button_style = button_style;
}
if let Some(layout) = styles.layout {
self.layout = layout;
}
if let Some(ok_text) = styles.ok_text {
self.ok_text = ok_text;
}
if let Some(no_cancel) = styles.no_cancel {
self.no_cancel = no_cancel;
}
if let Some(cancel_text) = styles.cancel_text {
self.cancel_text = cancel_text;
}
self
}
pub fn style(mut self, style: Style) -> Self {
self.block = self.block.style(style);
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = block;
self
}
pub fn border_style(mut self, style: Style) -> Self {
self.block = self.block.border_style(style);
self
}
pub fn title_style(mut self, style: Style) -> Self {
self.block = self.block.title_style(style);
self
}
pub fn button_style(mut self, style: ButtonStyle) -> Self {
self.button_style = style;
self
}
pub fn ok_text(mut self, str: &'a str) -> Self {
self.ok_text = str;
self
}
pub fn no_cancel(mut self) -> Self {
self.no_cancel = true;
self
}
pub fn cancel_text(mut self, str: &'a str) -> Self {
self.cancel_text = str;
self
}
pub fn left(mut self, left: Constraint) -> Self {
self.layout = self.layout.left(left);
self
}
pub fn top(mut self, top: Constraint) -> Self {
self.layout = self.layout.top(top);
self
}
pub fn right(mut self, right: Constraint) -> Self {
self.layout = self.layout.right(right);
self
}
pub fn bottom(mut self, bottom: Constraint) -> Self {
self.layout = self.layout.bottom(bottom);
self
}
pub fn position(mut self, pos: Position) -> Self {
self.layout = self.layout.position(pos);
self
}
pub fn width(mut self, width: Constraint) -> Self {
self.layout = self.layout.width(width);
self
}
pub fn height(mut self, height: Constraint) -> Self {
self.layout = self.layout.height(height);
self
}
pub fn size(mut self, size: Size) -> Self {
self.layout = self.layout.size(size);
self
}
pub fn layout_size(&self, area: Rect) -> Rect {
let l_dlg = if self.no_cancel {
self.layout.layout_dialog(
area,
block_padding2(&self.block),
[Constraint::Length(10)],
1,
Flex::End,
)
} else {
self.layout.layout_dialog(
area,
block_padding2(&self.block),
[Constraint::Length(12), Constraint::Length(10)],
1,
Flex::End,
)
};
l_dlg.widget_for(DialogItem::Content)
}
}
impl<'a> StatefulWidget for DialogFrame<'a> {
type State = DialogFrameState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
let l_dlg = if self.no_cancel {
self.layout.layout_dialog(
area,
block_padding2(&self.block),
[Constraint::Length(10)],
1,
Flex::End,
)
} else {
self.layout.layout_dialog(
area,
block_padding2(&self.block),
[Constraint::Length(12), Constraint::Length(10)],
1,
Flex::End,
)
};
state.area = l_dlg.area();
state.widget_area = l_dlg.widget_for(DialogItem::Content);
state.no_cancel = self.no_cancel;
reset_buf_area(l_dlg.area(), buf);
self.block.render(state.area, buf);
if self.no_cancel {
Button::new(self.ok_text).styles(self.button_style).render(
l_dlg.widget_for(DialogItem::Button(0)),
buf,
&mut state.ok,
);
} else {
Button::new(self.cancel_text)
.styles(self.button_style.clone())
.render(
l_dlg.widget_for(DialogItem::Button(0)),
buf,
&mut state.cancel,
);
Button::new(self.ok_text).styles(self.button_style).render(
l_dlg.widget_for(DialogItem::Button(1)),
buf,
&mut state.ok,
);
}
}
}
impl Default for DialogFrameState {
fn default() -> Self {
let z = Self {
area: Default::default(),
widget_area: Default::default(),
ok: Default::default(),
no_cancel: Default::default(),
cancel: Default::default(),
non_exhaustive: NonExhaustive,
};
z.ok.focus.set(true);
z
}
}
impl HasFocus for DialogFrameState {
fn build(&self, builder: &mut FocusBuilder) {
builder.widget(&self.ok);
if !self.no_cancel {
builder.widget(&self.cancel);
}
}
fn focus(&self) -> FocusFlag {
unimplemented!()
}
fn area(&self) -> Rect {
unimplemented!()
}
}
impl HasScreenCursor for DialogFrameState {
fn screen_cursor(&self) -> Option<(u16, u16)> {
None
}
}
impl RelocatableState for DialogFrameState {
fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
self.area.relocate(shift, clip);
self.widget_area.relocate(shift, clip);
self.ok.relocate(shift, clip);
self.cancel.relocate(shift, clip);
}
}
impl DialogFrameState {
pub fn new() -> Self {
Self::default()
}
pub fn named(_name: &str) -> Self {
Self::default()
}
}
pub enum DialogOutcome {
Continue,
Unchanged,
Changed,
Ok,
Cancel,
}
impl ConsumedEvent for DialogOutcome {
fn is_consumed(&self) -> bool {
!matches!(self, DialogOutcome::Continue)
}
}
impl From<DialogOutcome> for Outcome {
fn from(value: DialogOutcome) -> Self {
match value {
DialogOutcome::Continue => Outcome::Continue,
DialogOutcome::Unchanged => Outcome::Unchanged,
DialogOutcome::Changed => Outcome::Changed,
DialogOutcome::Ok => Outcome::Changed,
DialogOutcome::Cancel => Outcome::Changed,
}
}
}
impl From<Outcome> for DialogOutcome {
fn from(value: Outcome) -> Self {
match value {
Outcome::Continue => DialogOutcome::Continue,
Outcome::Unchanged => DialogOutcome::Unchanged,
Outcome::Changed => DialogOutcome::Changed,
}
}
}
impl HandleEvent<Event, Dialog, DialogOutcome> for DialogFrameState {
fn handle(&mut self, event: &Event, _: Dialog) -> DialogOutcome {
flow!({
if !self.no_cancel {
match self.cancel.handle(event, Regular) {
ButtonOutcome::Pressed => DialogOutcome::Cancel,
r => Outcome::from(r).into(),
}
} else {
DialogOutcome::Continue
}
});
flow!(match self.ok.handle(event, Regular) {
ButtonOutcome::Pressed => {
DialogOutcome::Ok
}
r => Outcome::from(r).into(),
});
flow!(match event {
ct_event!(keycode press Esc) if !self.no_cancel => {
DialogOutcome::Cancel
}
ct_event!(keycode press Enter) | ct_event!(keycode press F(12)) => {
DialogOutcome::Ok
}
_ => DialogOutcome::Unchanged,
});
DialogOutcome::Unchanged
}
}
impl HandleEvent<Event, MouseOnly, DialogOutcome> for DialogFrameState {
fn handle(&mut self, event: &Event, _: MouseOnly) -> DialogOutcome {
flow!({
if !self.no_cancel {
match self.cancel.handle(event, MouseOnly) {
ButtonOutcome::Pressed => DialogOutcome::Cancel,
r => Outcome::from(r).into(),
}
} else {
DialogOutcome::Continue
}
});
flow!(match self.ok.handle(event, MouseOnly) {
ButtonOutcome::Pressed => {
DialogOutcome::Ok
}
r => Outcome::from(r).into(),
});
DialogOutcome::Unchanged
}
}
pub fn handle_events(state: &mut DialogFrameState, _focus: bool, event: &Event) -> DialogOutcome {
HandleEvent::handle(state, event, Dialog)
}
pub fn handle_mouse_events(state: &mut DialogFrameState, event: &Event) -> DialogOutcome {
HandleEvent::handle(state, event, MouseOnly)
}