use crate::event::Popup;
use crate::menuline::{MenuLine, MenuLineState, MenuOutcome, MenuStyle};
use crate::popup_menu::{Placement, PopupMenu, PopupMenuState};
use crate::util::menu_str;
use rat_event::{flow, FocusKeys, HandleEvent, MouseOnly};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::{Line, StatefulWidget, Style};
use ratatui::widgets::{Block, StatefulWidgetRef};
use std::fmt::{Debug, Formatter};
pub trait MenuStructure<'a> {
fn menus(&'a self) -> Vec<(Line<'a>, Option<char>)>;
fn submenu(&'a self, n: usize) -> Vec<(Line<'a>, Option<char>)>;
}
#[derive(Debug)]
pub struct StaticMenu {
pub menu: &'static [(&'static str, &'static [&'static str])],
}
impl MenuStructure<'static> for StaticMenu {
fn menus(&'static self) -> Vec<(Line<'static>, Option<char>)> {
self.menu.iter().map(|v| menu_str(v.0)).collect()
}
fn submenu(&'static self, n: usize) -> Vec<(Line<'static>, Option<char>)> {
self.menu[n].1.iter().map(|v| menu_str(v)).collect()
}
}
#[derive(Debug, Default, Clone)]
pub struct MenuBar<'a> {
menu: MenuLine<'a>,
}
#[derive(Default, Clone)]
pub struct MenuPopup<'a> {
structure: Option<&'a dyn MenuStructure<'a>>,
popup: PopupMenu<'a>,
}
#[derive(Debug, Default, Clone)]
pub struct MenuBarState {
pub menu: MenuLineState,
pub popup_active: bool,
pub popup: PopupMenuState,
}
impl<'a> MenuBar<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn title(mut self, title: impl Into<Line<'a>>) -> Self {
self.menu = self.menu.title(title);
self
}
pub fn menu(mut self, structure: &'a dyn MenuStructure<'a>) -> Self {
for (m, n) in structure.menus() {
self.menu = self.menu.add(m, n);
}
self
}
#[inline]
pub fn styles(mut self, styles: MenuStyle) -> Self {
self.menu = self.menu.styles(styles.clone());
self
}
#[inline]
pub fn style(mut self, style: Style) -> Self {
self.menu = self.menu.style(style);
self
}
#[inline]
pub fn title_style(mut self, style: Style) -> Self {
self.menu = self.menu.title_style(style);
self
}
#[inline]
pub fn select_style(mut self, style: Style) -> Self {
self.menu = self.menu.select_style(style);
self
}
#[inline]
pub fn focus_style(mut self, style: Style) -> Self {
self.menu = self.menu.focus_style(style);
self
}
}
impl<'a> Debug for MenuPopup<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MenuPopup")
.field("popup", &self.popup)
.finish()
}
}
impl<'a> MenuPopup<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn menu(mut self, structure: &'a dyn MenuStructure<'a>) -> Self {
self.structure = Some(structure);
self
}
pub fn width(mut self, width: u16) -> Self {
self.popup = self.popup.width(width);
self
}
pub fn placement(mut self, placement: Placement) -> Self {
self.popup = self.popup.placement(placement);
self
}
#[inline]
pub fn styles(mut self, styles: MenuStyle) -> Self {
self.popup = self.popup.styles(styles.clone());
self
}
pub fn style(mut self, style: Style) -> Self {
self.popup = self.popup.style(style);
self
}
pub fn focus_style(mut self, style: Style) -> Self {
self.popup = self.popup.focus_style(style);
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.popup = self.popup.block(block);
self
}
}
impl<'a> StatefulWidgetRef for MenuBar<'a> {
type State = MenuBarState;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.menu.render_ref(area, buf, &mut state.menu);
}
}
impl<'a> StatefulWidget for MenuBar<'a> {
type State = MenuBarState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
self.menu.render(area, buf, &mut state.menu);
}
}
impl<'a> StatefulWidgetRef for MenuPopup<'a> {
type State = MenuBarState;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_menu_popup(self, area, buf, state);
}
}
impl<'a> StatefulWidget for MenuPopup<'a> {
type State = MenuBarState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_menu_popup(&self, area, buf, state);
}
}
fn render_menu_popup(
widget: &MenuPopup<'_>,
_area: Rect,
buf: &mut Buffer,
state: &mut MenuBarState,
) {
if state.popup_active {
if let Some(selected) = state.menu.selected {
if let Some(structure) = widget.structure {
let mut len = 0;
let mut popup = widget.popup.clone(); for (item, navchar) in structure.submenu(selected) {
popup = popup.add(item, navchar);
len += 1;
}
if len > 0 {
let area = state.menu.item_areas[selected];
popup.render(area, buf, &mut state.popup);
}
} else {
state.popup = Default::default();
}
} else {
state.popup = Default::default();
}
} else {
state.popup = Default::default();
}
}
impl MenuBarState {
pub fn new() -> Self {
Self::default()
}
pub fn popup_active(&self) -> bool {
self.popup_active
}
pub fn set_popup_active(&mut self, active: bool) {
self.popup_active = active;
}
}
impl HandleEvent<crossterm::event::Event, Popup, MenuOutcome> for MenuBarState {
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> MenuOutcome {
if let Some(selected) = self.menu.selected {
if self.popup_active {
match self.popup.handle(event, Popup) {
MenuOutcome::Selected(n) => MenuOutcome::MenuSelected(selected, n),
MenuOutcome::Activated(n) => MenuOutcome::MenuActivated(selected, n),
r => r,
}
} else {
MenuOutcome::NotUsed
}
} else {
MenuOutcome::NotUsed
}
}
}
impl HandleEvent<crossterm::event::Event, FocusKeys, MenuOutcome> for MenuBarState {
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: FocusKeys) -> MenuOutcome {
let old_selected = self.menu.selected();
let r = if self.menu.is_focused() {
self.menu.handle(event, FocusKeys)
} else {
self.menu.handle(event, MouseOnly)
};
match r {
MenuOutcome::Selected(n) => {
if old_selected == Some(n) {
self.popup_active = !self.popup_active;
}
}
_ => {}
};
r
}
}
impl HandleEvent<crossterm::event::Event, MouseOnly, MenuOutcome> for MenuBarState {
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> MenuOutcome {
flow!(if let Some(selected) = self.menu.selected {
if self.popup_active {
match self.popup.handle(event, MouseOnly) {
MenuOutcome::Selected(n) => MenuOutcome::MenuSelected(selected, n),
MenuOutcome::Activated(n) => MenuOutcome::MenuActivated(selected, n),
r => r,
}
} else {
MenuOutcome::NotUsed
}
} else {
MenuOutcome::NotUsed
});
let old_selected = self.menu.selected();
let r = self.menu.handle(event, MouseOnly);
match r {
MenuOutcome::Selected(n) => {
if old_selected == Some(n) {
self.popup_active = !self.popup_active;
}
}
_ => {}
};
r
}
}