use crate::_private::NonExhaustive;
use crate::choice::core::ChoiceCore;
use crate::event::ChoiceOutcome;
use crate::util::{block_size, revert_style};
use rat_event::util::{item_at, mouse_trap, MouseFlags};
use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Outcome, Popup, Regular};
use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
use rat_popup::event::PopupOutcome;
use rat_popup::{Placement, PopupCore, PopupCoreState, PopupStyle};
use rat_reloc::{relocate_area, relocate_areas, RelocatableState};
use rat_scrolled::event::ScrollOutcome;
use rat_scrolled::{Scroll, ScrollAreaState};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::prelude::BlockExt;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
#[cfg(feature = "unstable-widget-ref")]
use ratatui::widgets::StatefulWidgetRef;
use ratatui::widgets::{Block, StatefulWidget, Widget};
use std::cell::RefCell;
use std::cmp::{max, min};
use std::marker::PhantomData;
use std::rc::Rc;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ChoiceSelect {
#[default]
MouseScroll,
MouseMove,
MouseClick,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum ChoiceClose {
#[default]
SingleClick,
DoubleClick,
}
#[derive(Debug, Clone)]
pub struct Choice<'a, T>
where
T: PartialEq + Clone + Default,
{
values: Rc<RefCell<Vec<T>>>,
default_value: Option<T>,
items: Rc<RefCell<Vec<Line<'a>>>>,
style: Style,
button_style: Option<Style>,
select_style: Option<Style>,
focus_style: Option<Style>,
block: Option<Block<'a>>,
popup_placement: Placement,
popup_len: Option<u16>,
popup: PopupCore<'a>,
behave_select: ChoiceSelect,
behave_close: ChoiceClose,
}
#[derive(Debug)]
pub struct ChoiceWidget<'a, T>
where
T: PartialEq,
{
values: Rc<RefCell<Vec<T>>>,
default_value: Option<T>,
items: Rc<RefCell<Vec<Line<'a>>>>,
style: Style,
button_style: Option<Style>,
focus_style: Option<Style>,
block: Option<Block<'a>>,
len: Option<u16>,
behave_select: ChoiceSelect,
behave_close: ChoiceClose,
_phantom: PhantomData<T>,
}
#[derive(Debug)]
pub struct ChoicePopup<'a, T>
where
T: PartialEq,
{
items: Rc<RefCell<Vec<Line<'a>>>>,
style: Style,
select_style: Option<Style>,
popup_placement: Placement,
popup_len: Option<u16>,
popup: PopupCore<'a>,
_phantom: PhantomData<T>,
}
#[derive(Debug, Clone)]
pub struct ChoiceStyle {
pub style: Style,
pub button: Option<Style>,
pub select: Option<Style>,
pub focus: Option<Style>,
pub block: Option<Block<'static>>,
pub popup: PopupStyle,
pub popup_len: Option<u16>,
pub behave_select: Option<ChoiceSelect>,
pub behave_close: Option<ChoiceClose>,
pub non_exhaustive: NonExhaustive,
}
#[derive(Debug)]
pub struct ChoiceState<T = usize>
where
T: PartialEq + Clone + Default,
{
pub area: Rect,
pub nav_char: Vec<Vec<char>>,
pub item_area: Rect,
pub button_area: Rect,
pub item_areas: Vec<Rect>,
pub core: ChoiceCore<T>,
pub popup: PopupCoreState,
pub behave_select: ChoiceSelect,
pub behave_close: ChoiceClose,
pub focus: FocusFlag,
pub mouse: MouseFlags,
pub non_exhaustive: NonExhaustive,
}
pub(crate) mod event {
use rat_event::{ConsumedEvent, Outcome};
use rat_popup::event::PopupOutcome;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ChoiceOutcome {
Continue,
Unchanged,
Changed,
Value,
}
impl ConsumedEvent for ChoiceOutcome {
fn is_consumed(&self) -> bool {
*self != ChoiceOutcome::Continue
}
}
impl From<ChoiceOutcome> for Outcome {
fn from(value: ChoiceOutcome) -> Self {
match value {
ChoiceOutcome::Continue => Outcome::Continue,
ChoiceOutcome::Unchanged => Outcome::Unchanged,
ChoiceOutcome::Changed => Outcome::Changed,
ChoiceOutcome::Value => Outcome::Changed,
}
}
}
impl From<PopupOutcome> for ChoiceOutcome {
fn from(value: PopupOutcome) -> Self {
match value {
PopupOutcome::Continue => ChoiceOutcome::Continue,
PopupOutcome::Unchanged => ChoiceOutcome::Unchanged,
PopupOutcome::Changed => ChoiceOutcome::Changed,
PopupOutcome::Hide => ChoiceOutcome::Changed,
}
}
}
}
pub mod core {
#[derive(Debug, Default, Clone)]
pub struct ChoiceCore<T>
where
T: PartialEq + Clone + Default,
{
values: Vec<T>,
default_value: Option<T>,
value: T,
selected: Option<usize>,
}
impl<T> ChoiceCore<T>
where
T: PartialEq + Clone + Default,
{
pub fn set_values(&mut self, values: Vec<T>) {
self.values = values;
if self.values.is_empty() {
self.selected = None;
} else {
self.selected = self.values.iter().position(|v| *v == self.value);
}
}
pub fn values(&self) -> &[T] {
&self.values
}
pub fn set_default_value(&mut self, default_value: Option<T>) {
self.default_value = default_value.clone();
}
pub fn default_value(&self) -> &Option<T> {
&self.default_value
}
pub fn selected(&self) -> Option<usize> {
self.selected
}
pub fn set_selected(&mut self, select: usize) -> bool {
let old_sel = self.selected;
if self.values.is_empty() {
self.selected = None;
} else {
if let Some(value) = self.values.get(select) {
self.value = value.clone();
self.selected = Some(select);
} else {
self.selected = None;
}
}
old_sel != self.selected
}
pub fn set_value(&mut self, value: T) -> bool {
let old_value = self.value.clone();
self.value = value;
self.selected = self.values.iter().position(|v| *v == self.value);
old_value != self.value
}
pub fn value(&self) -> T {
self.value.clone()
}
pub fn clear(&mut self) -> bool {
let old_selected = self.selected;
let old_value = self.value.clone();
if let Some(default_value) = &self.default_value {
self.value = default_value.clone();
}
self.selected = self.values.iter().position(|v| *v == self.value);
old_selected != self.selected || old_value != self.value
}
}
}
impl Default for ChoiceStyle {
fn default() -> Self {
Self {
style: Default::default(),
button: None,
select: None,
focus: None,
block: None,
popup: Default::default(),
popup_len: None,
behave_select: None,
behave_close: None,
non_exhaustive: NonExhaustive,
}
}
}
impl<T> Default for Choice<'_, T>
where
T: PartialEq + Clone + Default,
{
fn default() -> Self {
Self {
values: Default::default(),
default_value: Default::default(),
items: Default::default(),
style: Default::default(),
button_style: Default::default(),
select_style: Default::default(),
focus_style: Default::default(),
block: Default::default(),
popup_len: Default::default(),
popup_placement: Placement::BelowOrAbove,
popup: Default::default(),
behave_select: Default::default(),
behave_close: Default::default(),
}
}
}
impl<'a> Choice<'a, usize> {
#[inline]
pub fn auto_items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = V>) -> Self {
{
let mut values = self.values.borrow_mut();
let mut itemz = self.items.borrow_mut();
values.clear();
itemz.clear();
for (k, v) in items.into_iter().enumerate() {
values.push(k);
itemz.push(v.into());
}
}
self
}
pub fn auto_item(self, item: impl Into<Line<'a>>) -> Self {
let idx = self.values.borrow().len();
self.values.borrow_mut().push(idx);
self.items.borrow_mut().push(item.into());
self
}
}
impl<'a, T> Choice<'a, T>
where
T: PartialEq + Clone + Default,
{
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn items<V: Into<Line<'a>>>(self, items: impl IntoIterator<Item = (T, V)>) -> Self {
{
let mut values = self.values.borrow_mut();
let mut itemz = self.items.borrow_mut();
values.clear();
itemz.clear();
for (k, v) in items.into_iter() {
values.push(k);
itemz.push(v.into());
}
}
self
}
pub fn item(self, value: T, item: impl Into<Line<'a>>) -> Self {
self.values.borrow_mut().push(value);
self.items.borrow_mut().push(item.into());
self
}
pub fn default_value(mut self, default: T) -> Self {
self.default_value = Some(default);
self
}
pub fn styles(mut self, styles: ChoiceStyle) -> Self {
self.style = styles.style;
if styles.button.is_some() {
self.button_style = styles.button;
}
if styles.select.is_some() {
self.select_style = styles.select;
}
if styles.focus.is_some() {
self.focus_style = styles.focus;
}
if styles.block.is_some() {
self.block = styles.block;
}
if let Some(select) = styles.behave_select {
self.behave_select = select;
}
if let Some(close) = styles.behave_close {
self.behave_close = close;
}
self.block = self.block.map(|v| v.style(self.style));
if let Some(placement) = styles.popup.placement {
self.popup_placement = placement;
}
if styles.popup_len.is_some() {
self.popup_len = styles.popup_len;
}
self.popup = self.popup.styles(styles.popup);
self
}
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self.block = self.block.map(|v| v.style(self.style));
self
}
pub fn button_style(mut self, style: Style) -> Self {
self.button_style = Some(style);
self
}
pub fn select_style(mut self, style: Style) -> Self {
self.select_style = Some(style);
self
}
pub fn focus_style(mut self, style: Style) -> Self {
self.focus_style = Some(style);
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.block = Some(block);
self.block = self.block.map(|v| v.style(self.style));
self
}
pub fn popup_placement(mut self, placement: Placement) -> Self {
self.popup_placement = placement;
self
}
pub fn popup_boundary(mut self, boundary: Rect) -> Self {
self.popup = self.popup.boundary(boundary);
self
}
pub fn popup_len(mut self, len: u16) -> Self {
self.popup_len = Some(len);
self
}
pub fn popup_style(mut self, style: Style) -> Self {
self.popup = self.popup.style(style);
self
}
pub fn popup_block(mut self, block: Block<'a>) -> Self {
self.popup = self.popup.block(block);
self
}
pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
self.popup = self.popup.v_scroll(scroll);
self
}
pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
self.popup = self.popup.offset(offset);
self
}
pub fn popup_x_offset(mut self, offset: i16) -> Self {
self.popup = self.popup.x_offset(offset);
self
}
pub fn popup_y_offset(mut self, offset: i16) -> Self {
self.popup = self.popup.y_offset(offset);
self
}
pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
self.behave_select = select;
self
}
pub fn behave_close(mut self, close: ChoiceClose) -> Self {
self.behave_close = close;
self
}
pub fn width(&self) -> u16 {
let w = self
.items
.borrow()
.iter()
.map(|v| v.width())
.max()
.unwrap_or_default();
w as u16 + block_size(&self.block).width
}
pub fn height(&self) -> u16 {
1 + block_size(&self.block).height
}
pub fn into_widgets(self) -> (ChoiceWidget<'a, T>, ChoicePopup<'a, T>) {
(
ChoiceWidget {
values: self.values,
default_value: self.default_value,
items: self.items.clone(),
style: self.style,
button_style: self.button_style,
focus_style: self.focus_style,
block: self.block,
len: self.popup_len,
behave_select: self.behave_select,
behave_close: self.behave_close,
_phantom: Default::default(),
},
ChoicePopup {
items: self.items.clone(),
style: self.style,
select_style: self.select_style,
popup: self.popup,
popup_placement: self.popup_placement,
popup_len: self.popup_len,
_phantom: Default::default(),
},
)
}
}
#[cfg(feature = "unstable-widget-ref")]
impl<'a, T> StatefulWidgetRef for ChoiceWidget<'a, T>
where
T: PartialEq + Clone + Default,
{
type State = ChoiceState<T>;
fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_choice(self, area, buf, state);
state.core.set_values(self.values.borrow().clone());
if let Some(default_value) = self.default_value.clone() {
state.core.set_default_value(Some(default_value));
}
}
}
impl<T> StatefulWidget for ChoiceWidget<'_, T>
where
T: PartialEq + Clone + Default,
{
type State = ChoiceState<T>;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_choice(&self, area, buf, state);
state.core.set_values(self.values.take());
if let Some(default_value) = self.default_value {
state.core.set_default_value(Some(default_value));
}
}
}
fn render_choice<T: PartialEq + Clone + Default>(
widget: &ChoiceWidget<'_, T>,
area: Rect,
buf: &mut Buffer,
state: &mut ChoiceState<T>,
) {
state.area = area;
state.behave_select = widget.behave_select;
state.behave_close = widget.behave_close;
if !state.popup.is_active() {
let len = widget
.len
.unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
state.popup.v_scroll.max_offset = widget.items.borrow().len().saturating_sub(len as usize);
state.popup.v_scroll.page_len = len as usize;
if let Some(selected) = state.core.selected() {
state.popup.v_scroll.scroll_to_pos(selected);
}
}
state.nav_char.clear();
state.nav_char.extend(widget.items.borrow().iter().map(|v| {
v.spans
.first()
.and_then(|v| v.content.as_ref().chars().next())
.map_or(Vec::default(), |c| c.to_lowercase().collect::<Vec<_>>())
}));
let inner = widget.block.inner_if_some(area);
state.item_area = Rect::new(
inner.x,
inner.y,
inner.width.saturating_sub(3),
inner.height,
);
state.button_area = Rect::new(
inner.right().saturating_sub(min(3, inner.width)),
inner.y,
3,
inner.height,
);
let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
if state.is_focused() {
if widget.block.is_some() {
widget.block.render(area, buf);
}
buf.set_style(inner, focus_style);
} else {
if widget.block.is_some() {
widget.block.render(area, buf);
} else {
buf.set_style(inner, widget.style);
}
if let Some(button_style) = widget.button_style {
buf.set_style(state.button_area, button_style);
}
}
if let Some(selected) = state.core.selected() {
if let Some(item) = widget.items.borrow().get(selected) {
item.render(state.item_area, buf);
}
}
let dy = if (state.button_area.height & 1) == 1 {
state.button_area.height / 2
} else {
state.button_area.height.saturating_sub(1) / 2
};
let bc = if state.is_popup_active() {
" ◆ "
} else {
" ▼ "
};
Span::from(bc).render(
Rect::new(state.button_area.x, state.button_area.y + dy, 3, 1),
buf,
);
}
impl<T> StatefulWidget for ChoicePopup<'_, T>
where
T: PartialEq + Clone + Default,
{
type State = ChoiceState<T>;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_popup(&self, area, buf, state);
}
}
fn render_popup<T: PartialEq + Clone + Default>(
widget: &ChoicePopup<'_, T>,
area: Rect,
buf: &mut Buffer,
state: &mut ChoiceState<T>,
) {
if state.popup.is_active() {
let len = widget
.popup_len
.unwrap_or_else(|| min(5, widget.items.borrow().len()) as u16);
let popup_len = len + widget.popup.get_block_size().height;
let popup_style = widget.popup.style;
let pop_area = Rect::new(0, 0, area.width, popup_len);
widget
.popup
.ref_constraint(widget.popup_placement.into_constraint(area))
.render(pop_area, buf, &mut state.popup);
let inner = state.popup.widget_area;
state.popup.v_scroll.max_offset = widget
.items
.borrow()
.len()
.saturating_sub(inner.height as usize);
state.popup.v_scroll.page_len = inner.height as usize;
state.item_areas.clear();
let mut row = inner.y;
let mut idx = state.popup.v_scroll.offset;
loop {
if row >= inner.bottom() {
break;
}
let item_area = Rect::new(inner.x, row, inner.width, 1);
state.item_areas.push(item_area);
if let Some(item) = widget.items.borrow().get(idx) {
let style = if state.core.selected() == Some(idx) {
widget.select_style.unwrap_or(revert_style(widget.style))
} else {
popup_style
};
buf.set_style(item_area, style);
item.render(item_area, buf);
} else {
}
row += 1;
idx += 1;
}
} else {
state.popup.clear_areas();
}
}
impl<T> Clone for ChoiceState<T>
where
T: PartialEq + Clone + Default,
{
fn clone(&self) -> Self {
Self {
area: self.area,
nav_char: self.nav_char.clone(),
item_area: self.item_area,
button_area: self.button_area,
item_areas: self.item_areas.clone(),
core: self.core.clone(),
popup: self.popup.clone(),
behave_select: self.behave_select,
behave_close: self.behave_close,
focus: FocusFlag::named(self.focus.name()),
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl<T> Default for ChoiceState<T>
where
T: PartialEq + Clone + Default,
{
fn default() -> Self {
Self {
area: Default::default(),
nav_char: Default::default(),
item_area: Default::default(),
button_area: Default::default(),
item_areas: Default::default(),
core: Default::default(),
popup: Default::default(),
behave_select: Default::default(),
behave_close: Default::default(),
focus: Default::default(),
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl<T> HasFocus for ChoiceState<T>
where
T: PartialEq + Clone + Default,
{
fn build(&self, builder: &mut FocusBuilder) {
builder.append_flags(self.focus(), self.area(), 0, self.navigable());
builder.append_flags(self.focus(), self.popup.area, 1, Navigation::Mouse);
}
fn focus(&self) -> FocusFlag {
self.focus.clone()
}
fn area(&self) -> Rect {
self.area
}
}
impl<T> RelocatableState for ChoiceState<T>
where
T: PartialEq + Clone + Default,
{
fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
self.area = relocate_area(self.area, shift, clip);
self.item_area = relocate_area(self.item_area, shift, clip);
self.button_area = relocate_area(self.button_area, shift, clip);
relocate_areas(&mut self.item_areas, shift, clip);
self.popup.relocate(shift, clip);
}
}
impl<T> ChoiceState<T>
where
T: PartialEq + Clone + Default,
{
pub fn new() -> Self {
Self::default()
}
pub fn named(name: &str) -> Self {
Self {
focus: FocusFlag::named(name),
..Default::default()
}
}
pub fn is_popup_active(&self) -> bool {
self.popup.is_active()
}
pub fn flip_popup_active(&mut self) {
self.popup.flip_active();
}
pub fn set_popup_active(&mut self, active: bool) -> bool {
let old_active = self.popup.is_active();
self.popup.set_active(active);
old_active != active
}
pub fn set_default_value(&mut self, default_value: Option<T>) {
self.core.set_default_value(default_value);
}
pub fn default_value(&self) -> &Option<T> {
self.core.default_value()
}
pub fn set_value(&mut self, value: T) -> bool {
self.core.set_value(value)
}
pub fn value(&self) -> T {
self.core.value()
}
pub fn clear(&mut self) -> bool {
self.core.clear()
}
pub fn select(&mut self, select: usize) -> bool {
self.core.set_selected(select)
}
pub fn selected(&self) -> Option<usize> {
self.core.selected()
}
pub fn is_empty(&self) -> bool {
self.core.values().is_empty()
}
pub fn len(&self) -> usize {
self.core.values().len()
}
pub fn clear_offset(&mut self) {
self.popup.v_scroll.set_offset(0);
}
pub fn set_offset(&mut self, offset: usize) -> bool {
self.popup.v_scroll.set_offset(offset)
}
pub fn offset(&self) -> usize {
self.popup.v_scroll.offset()
}
pub fn max_offset(&self) -> usize {
self.popup.v_scroll.max_offset()
}
pub fn page_len(&self) -> usize {
self.popup.v_scroll.page_len()
}
pub fn scroll_by(&self) -> usize {
self.popup.v_scroll.scroll_by()
}
pub fn scroll_to_selected(&mut self) -> bool {
if let Some(selected) = self.core.selected() {
self.popup.v_scroll.scroll_to_pos(selected)
} else {
false
}
}
}
impl<T> ChoiceState<T>
where
T: PartialEq + Clone + Default,
{
pub fn select_by_char(&mut self, c: char) -> bool {
if self.nav_char.is_empty() {
return false;
}
let c = c.to_lowercase().collect::<Vec<_>>();
let mut idx = self.core.selected().expect("selected") + 1;
loop {
if idx >= self.nav_char.len() {
idx = 0;
}
if Some(idx) == self.core.selected() {
break;
}
if self.nav_char[idx] == c {
self.core.set_selected(idx);
return true;
}
idx += 1;
}
false
}
pub fn move_to(&mut self, n: usize) -> bool {
let r1 = self.select(n);
let r2 = self.scroll_to_selected();
r1 || r2
}
pub fn move_down(&mut self, n: usize) -> bool {
let old_selected = self.core.selected();
let r2 = if let Some(selected) = self.core.selected() {
let select = (selected + n).clamp(0, self.core.values().len().saturating_sub(1));
self.core.set_selected(select);
self.scroll_to_selected()
} else {
false
};
old_selected != self.core.selected() || r2
}
pub fn move_up(&mut self, n: usize) -> bool {
let old_selected = self.core.selected();
let r2 = if let Some(selected) = self.core.selected() {
let select =
(selected.saturating_sub(n)).clamp(0, self.core.values().len().saturating_sub(1));
self.core.set_selected(select);
self.scroll_to_selected()
} else {
false
};
old_selected != self.core.selected() || r2
}
}
impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Regular, ChoiceOutcome>
for ChoiceState<T>
{
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> ChoiceOutcome {
if self.lost_focus() {
self.set_popup_active(false);
}
let r = if self.is_focused() {
match event {
ct_event!(key press ' ') => {
self.flip_popup_active();
ChoiceOutcome::Changed
}
ct_event!(key press c) => {
if self.select_by_char(*c) {
self.scroll_to_selected();
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
ct_event!(keycode press Enter) => {
self.flip_popup_active();
if !self.is_popup_active() {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Changed
}
}
ct_event!(keycode press Esc) => {
if self.set_popup_active(false) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Continue
}
}
ct_event!(keycode press Delete) | ct_event!(keycode press Backspace) => {
self.clear();
ChoiceOutcome::Value
}
ct_event!(keycode press Down) => {
let r0 = if !self.popup.is_active() {
self.popup.set_active(true);
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Continue
};
let r1 = if self.move_down(1) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
};
max(r0, r1)
}
ct_event!(keycode press Up) => {
let r0 = if !self.popup.is_active() {
self.popup.set_active(true);
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Continue
};
let r1 = if self.move_up(1) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
};
max(r0, r1)
}
_ => ChoiceOutcome::Continue,
}
} else {
ChoiceOutcome::Continue
};
if !r.is_consumed() {
self.handle(event, MouseOnly)
} else {
r
}
}
}
impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, MouseOnly, ChoiceOutcome>
for ChoiceState<T>
{
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> ChoiceOutcome {
let r = match event {
ct_event!(mouse down Left for x,y)
if self.item_area.contains((*x, *y).into())
|| self.button_area.contains((*x, *y).into()) =>
{
if !self.gained_focus() && !self.is_popup_active() && !self.popup.active.lost() {
self.set_popup_active(true);
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Continue
}
}
_ => ChoiceOutcome::Continue,
};
self.popup.active.set_lost(false);
self.popup.active.set_gained(false);
r
}
}
impl<T: PartialEq + Clone + Default> HandleEvent<crossterm::event::Event, Popup, ChoiceOutcome>
for ChoiceState<T>
{
fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> ChoiceOutcome {
let r1 = match self.popup.handle(event, Popup) {
PopupOutcome::Hide => {
self.set_popup_active(false);
ChoiceOutcome::Changed
}
r => r.into(),
};
let r2 = match self.behave_select {
ChoiceSelect::MouseScroll => {
let mut sas = ScrollAreaState::new()
.area(self.popup.area)
.v_scroll(&mut self.popup.v_scroll);
let mut r = match sas.handle(event, MouseOnly) {
ScrollOutcome::Up(n) => {
if self.move_up(n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
ScrollOutcome::Down(n) => {
if self.move_down(n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
ScrollOutcome::VPos(n) => {
if self.move_to(n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
};
r = r.or_else(|| match event {
ct_event!(mouse down Left for x,y)
if self.popup.widget_area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&self.item_areas, *x, *y) {
if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
} else {
ChoiceOutcome::Unchanged
}
}
ct_event!(mouse drag Left for x,y)
if self.popup.widget_area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&self.item_areas, *x, *y) {
if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
});
r
}
ChoiceSelect::MouseMove => {
let mut r = if let Some(selected) = self.core.selected() {
let rel_sel = selected.saturating_sub(self.offset());
let mut sas = ScrollAreaState::new()
.area(self.popup.area)
.v_scroll(&mut self.popup.v_scroll);
match sas.handle(event, MouseOnly) {
ScrollOutcome::Up(n) => {
self.popup.v_scroll.scroll_up(n);
if self.select(self.offset() + rel_sel) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
ScrollOutcome::Down(n) => {
self.popup.v_scroll.scroll_down(n);
if self.select(self.offset() + rel_sel) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
ScrollOutcome::VPos(n) => {
if self.popup.v_scroll.set_offset(n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
}
} else {
ChoiceOutcome::Continue
};
r = r.or_else(|| match event {
ct_event!(mouse moved for x,y)
if self.popup.widget_area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&self.item_areas, *x, *y) {
if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
});
r
}
ChoiceSelect::MouseClick => {
let mut sas = ScrollAreaState::new()
.area(self.popup.area)
.v_scroll(&mut self.popup.v_scroll);
let mut r = match sas.handle(event, MouseOnly) {
ScrollOutcome::Up(n) => {
if self.popup.v_scroll.scroll_up(n) {
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Unchanged
}
}
ScrollOutcome::Down(n) => {
if self.popup.v_scroll.scroll_down(n) {
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Unchanged
}
}
ScrollOutcome::VPos(n) => {
if self.popup.v_scroll.set_offset(n) {
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
};
r = r.or_else(|| match event {
ct_event!(mouse down Left for x,y)
if self.popup.widget_area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&self.item_areas, *x, *y) {
if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
} else {
ChoiceOutcome::Unchanged
}
}
ct_event!(mouse drag Left for x,y)
if self.popup.widget_area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&self.item_areas, *x, *y) {
if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
}
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
});
r
}
};
let r3 = match self.behave_close {
ChoiceClose::SingleClick => match event {
ct_event!(mouse down Left for x,y)
if self.popup.widget_area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&self.item_areas, *x, *y) {
let r = if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
};
let s = if self.set_popup_active(false) {
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Unchanged
};
max(r, s)
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
},
ChoiceClose::DoubleClick => match event {
ct_event!(mouse any for m) if self.mouse.doubleclick(self.popup.widget_area, m) => {
if let Some(n) = item_at(&self.item_areas, m.column, m.row) {
let r = if self.move_to(self.offset() + n) {
ChoiceOutcome::Value
} else {
ChoiceOutcome::Unchanged
};
let s = if self.set_popup_active(false) {
ChoiceOutcome::Changed
} else {
ChoiceOutcome::Unchanged
};
max(r, s)
} else {
ChoiceOutcome::Unchanged
}
}
_ => ChoiceOutcome::Continue,
},
};
let mut r = max(r1, max(r2, r3));
r = r.or_else(|| match mouse_trap(event, self.popup.area) {
Outcome::Continue => ChoiceOutcome::Continue,
Outcome::Unchanged => ChoiceOutcome::Unchanged,
Outcome::Changed => ChoiceOutcome::Changed,
});
r
}
}
pub fn handle_popup<T: PartialEq + Clone + Default>(
state: &mut ChoiceState<T>,
focus: bool,
event: &crossterm::event::Event,
) -> ChoiceOutcome {
state.focus.set(focus);
HandleEvent::handle(state, event, Popup)
}
pub fn handle_events<T: PartialEq + Clone + Default>(
state: &mut ChoiceState<T>,
focus: bool,
event: &crossterm::event::Event,
) -> ChoiceOutcome {
state.focus.set(focus);
HandleEvent::handle(state, event, Regular)
}
pub fn handle_mouse_events<T: PartialEq + Clone + Default>(
state: &mut ChoiceState<T>,
event: &crossterm::event::Event,
) -> ChoiceOutcome {
HandleEvent::handle(state, event, MouseOnly)
}