use crate::_private::NonExhaustive;
use crate::choice::{
Choice, ChoiceClose, ChoiceFocus, ChoicePopup, ChoiceSelect, ChoiceState, ChoiceStyle,
ChoiceWidget,
};
use crate::combobox::event::ComboboxOutcome;
use crate::event::ChoiceOutcome;
use crate::text::HasScreenCursor;
use rat_event::util::{MouseFlags, item_at, mouse_trap};
use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Popup, Regular, ct_event};
use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
use rat_popup::Placement;
use rat_popup::event::PopupOutcome;
use rat_reloc::RelocatableState;
use rat_scrolled::event::ScrollOutcome;
use rat_scrolled::{Scroll, ScrollAreaState};
use rat_text::TextStyle;
use rat_text::event::TextOutcome;
use rat_text::text_input::{TextInput, TextInputState};
use ratatui_core::buffer::Buffer;
use ratatui_core::layout::{Alignment, Rect};
use ratatui_core::style::Style;
use ratatui_core::text::Line;
use ratatui_core::widgets::StatefulWidget;
use ratatui_crossterm::crossterm::event::Event;
use ratatui_widgets::block::Block;
use std::cmp::max;
#[derive(Debug, Clone)]
pub struct Combobox<'a> {
choice: Choice<'a, String>,
text: TextInput<'a>,
}
#[derive(Debug)]
pub struct ComboboxWidget<'a> {
choice: ChoiceWidget<'a, String>,
text: TextInput<'a>,
}
#[derive(Debug)]
pub struct ComboboxPopup<'a> {
choice: ChoicePopup<'a, String>,
}
#[derive(Debug, Clone)]
pub struct ComboboxStyle {
pub choice: ChoiceStyle,
pub text: TextStyle,
pub non_exhaustive: NonExhaustive,
}
#[derive(Debug)]
pub struct ComboboxState {
pub area: Rect,
pub inner: Rect,
pub choice: ChoiceState<String>,
pub text: TextInputState,
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 ComboboxOutcome {
Continue,
Unchanged,
Changed,
Value,
TextChanged,
}
impl ConsumedEvent for ComboboxOutcome {
fn is_consumed(&self) -> bool {
*self != ComboboxOutcome::Continue
}
}
impl From<Outcome> for ComboboxOutcome {
fn from(value: Outcome) -> Self {
match value {
Outcome::Continue => ComboboxOutcome::Continue,
Outcome::Unchanged => ComboboxOutcome::Unchanged,
Outcome::Changed => ComboboxOutcome::Changed,
}
}
}
impl From<ComboboxOutcome> for Outcome {
fn from(value: ComboboxOutcome) -> Self {
match value {
ComboboxOutcome::Continue => Outcome::Continue,
ComboboxOutcome::Unchanged => Outcome::Unchanged,
ComboboxOutcome::Changed => Outcome::Changed,
ComboboxOutcome::Value => Outcome::Changed,
ComboboxOutcome::TextChanged => Outcome::Changed,
}
}
}
impl From<PopupOutcome> for ComboboxOutcome {
fn from(value: PopupOutcome) -> Self {
match value {
PopupOutcome::Continue => ComboboxOutcome::Continue,
PopupOutcome::Unchanged => ComboboxOutcome::Unchanged,
PopupOutcome::Changed => ComboboxOutcome::Changed,
PopupOutcome::Hide => ComboboxOutcome::Changed,
}
}
}
}
impl Default for ComboboxStyle {
fn default() -> Self {
Self {
choice: Default::default(),
text: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl Default for Combobox<'_> {
fn default() -> Self {
Self {
choice: Choice::default().skip_item_render(true),
text: Default::default(),
}
}
}
impl<'a> Combobox<'a> {
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn items<V: Into<Line<'a>>>(
mut self,
items: impl IntoIterator<Item = (String, V)>,
) -> Self {
self.choice = self.choice.items(items);
self
}
pub fn item(mut self, value: impl Into<String>, item: impl Into<Line<'a>>) -> Self {
self.choice = self.choice.item(value.into(), item);
self
}
pub fn default_value(mut self, default: String) -> Self {
self.choice = self.choice.default_value(default);
self
}
pub fn styles(mut self, styles: ComboboxStyle) -> Self {
self.choice = self.choice.styles(styles.choice);
self.text = self.text.styles(styles.text);
self
}
pub fn style(mut self, style: Style) -> Self {
self.choice = self.choice.style(style);
self.text = self.text.style(style);
self
}
pub fn button_style(mut self, style: Style) -> Self {
self.choice = self.choice.button_style(style);
self
}
pub fn select_style(mut self, style: Style) -> Self {
self.choice = self.choice.select_style(style);
self
}
pub fn focus_style(mut self, style: Style) -> Self {
self.choice = self.choice.focus_style(style);
self
}
pub fn text_style(mut self, style: TextStyle) -> Self {
self.text = self.text.styles(style);
self
}
pub fn block(mut self, block: Block<'a>) -> Self {
self.choice = self.choice.block(block);
self
}
pub fn popup_alignment(mut self, alignment: Alignment) -> Self {
self.choice = self.choice.popup_alignment(alignment);
self
}
pub fn popup_placement(mut self, placement: Placement) -> Self {
self.choice = self.choice.popup_placement(placement);
self
}
pub fn popup_boundary(mut self, boundary: Rect) -> Self {
self.choice = self.choice.popup_boundary(boundary);
self
}
pub fn popup_len(mut self, len: u16) -> Self {
self.choice = self.choice.popup_len(len);
self
}
pub fn popup_style(mut self, style: Style) -> Self {
self.choice = self.choice.popup_style(style);
self
}
pub fn popup_block(mut self, block: Block<'a>) -> Self {
self.choice = self.choice.popup_block(block);
self
}
pub fn popup_scroll(mut self, scroll: Scroll<'a>) -> Self {
self.choice = self.choice.popup_scroll(scroll);
self
}
pub fn popup_offset(mut self, offset: (i16, i16)) -> Self {
self.choice = self.choice.popup_offset(offset);
self
}
pub fn popup_x_offset(mut self, offset: i16) -> Self {
self.choice = self.choice.popup_x_offset(offset);
self
}
pub fn popup_y_offset(mut self, offset: i16) -> Self {
self.choice = self.choice.popup_y_offset(offset);
self
}
pub fn behave_focus(mut self, focus: ChoiceFocus) -> Self {
self.choice = self.choice.behave_focus(focus);
self
}
pub fn behave_select(mut self, select: ChoiceSelect) -> Self {
self.choice = self.choice.behave_select(select);
self
}
pub fn behave_close(mut self, close: ChoiceClose) -> Self {
self.choice = self.choice.behave_close(close);
self
}
pub fn width(&self) -> u16 {
self.choice.width()
}
pub fn height(&self) -> u16 {
self.choice.height()
}
pub fn into_widgets(self) -> (ComboboxWidget<'a>, ComboboxPopup<'a>) {
let (choice, choice_popup) = self.choice.into_widgets();
(
ComboboxWidget {
choice,
text: self.text,
},
ComboboxPopup {
choice: choice_popup,
},
)
}
}
impl<'a> ComboboxWidget<'a> {
pub fn width(&self) -> u16 {
self.choice.width()
}
pub fn height(&self) -> u16 {
self.choice.height()
}
}
impl<'a> StatefulWidget for &ComboboxWidget<'a> {
type State = ComboboxState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_combobox(self, area, buf, state);
}
}
impl StatefulWidget for ComboboxWidget<'_> {
type State = ComboboxState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_combobox(&self, area, buf, state);
}
}
fn render_combobox(
widget: &ComboboxWidget<'_>,
area: Rect,
buf: &mut Buffer,
state: &mut ComboboxState,
) {
state.area = area;
(&widget.choice).render(area, buf, &mut state.choice);
state.inner = state.choice.inner;
(&widget.text).render(state.choice.item_area, buf, &mut state.text);
}
impl ComboboxPopup<'_> {
pub fn layout(&self, area: Rect, buf: &mut Buffer, state: &mut ComboboxState) -> Rect {
self.choice.layout(area, buf, &mut state.choice)
}
}
impl StatefulWidget for &ComboboxPopup<'_> {
type State = ComboboxState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_popup(self, area, buf, state);
}
}
impl StatefulWidget for ComboboxPopup<'_> {
type State = ComboboxState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
render_popup(&self, area, buf, state);
}
}
fn render_popup(
widget: &ComboboxPopup<'_>,
_area: Rect,
buf: &mut Buffer,
state: &mut ComboboxState,
) {
(&widget.choice).render(Rect::default(), buf, &mut state.choice);
}
impl Clone for ComboboxState {
fn clone(&self) -> Self {
let mut text = self.text.clone();
let mut choice = self.choice.clone();
let focus = focus_cb(self.focus.new_instance(), text.focus, choice.focus);
text.focus = focus.clone();
choice.focus = focus.clone();
Self {
area: self.area,
inner: self.inner,
choice,
text,
focus,
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
impl Default for ComboboxState {
fn default() -> Self {
let mut text = TextInputState::default();
let mut choice = ChoiceState::default();
let focus = focus_cb(FocusFlag::default(), text.focus, choice.focus);
text.focus = focus.clone();
choice.focus = focus.clone();
Self {
area: Default::default(),
inner: Default::default(),
choice,
text,
focus,
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
}
fn focus_cb(flag: FocusFlag, choice: FocusFlag, text: FocusFlag) -> FocusFlag {
let choice_clone = choice.clone();
let text_clone = text.clone();
flag.on_lost(move || {
choice_clone.call_on_lost();
text_clone.call_on_lost();
});
let choice_clone = choice.clone();
let text_clone = text.clone();
flag.on_gained(move || {
choice_clone.call_on_gained();
text_clone.call_on_gained();
});
flag
}
impl HasScreenCursor for ComboboxState {
fn screen_cursor(&self) -> Option<(u16, u16)> {
self.text.screen_cursor()
}
}
impl HasFocus for ComboboxState {
fn build(&self, builder: &mut FocusBuilder) {
builder.leaf_with_flags(self.focus(), self.area(), 0, self.navigable());
builder.leaf_with_flags(self.focus(), self.choice.popup.area, 1, Navigation::Mouse);
}
fn focus(&self) -> FocusFlag {
self.focus.clone()
}
fn area(&self) -> Rect {
self.area
}
}
impl RelocatableState for ComboboxState {
fn relocate(&mut self, _shift: (i16, i16), _clip: Rect) {
}
fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
self.area.relocate(shift, clip);
self.inner.relocate(shift, clip);
self.choice.relocate(shift, clip);
self.text.relocate(shift, clip);
self.choice.relocate_popup(shift, clip);
}
}
impl ComboboxState {
pub fn new() -> Self {
Self::default()
}
pub fn named(name: &str) -> Self {
let mut text = TextInputState::default();
let mut choice = ChoiceState::default();
let focus = focus_cb(FocusFlag::new().with_name(name), text.focus, choice.focus);
text.focus = focus.clone();
choice.focus = focus.clone();
Self {
area: Default::default(),
inner: Default::default(),
choice,
text,
focus,
mouse: Default::default(),
non_exhaustive: NonExhaustive,
}
}
pub fn is_popup_active(&self) -> bool {
self.choice.is_popup_active()
}
pub fn flip_popup_active(&mut self) {
self.choice.flip_popup_active();
}
pub fn set_popup_active(&mut self, active: bool) -> bool {
self.choice.set_popup_active(active)
}
pub fn set_default_value(&mut self, default_value: Option<String>) {
self.choice.set_default_value(default_value);
}
pub fn default_value(&self) -> &Option<String> {
self.choice.default_value()
}
pub fn set_value(&mut self, value: impl Into<String>) -> bool {
let value = value.into();
self.text.set_value(value.clone());
self.choice.set_value(value)
}
pub fn value(&self) -> String {
self.text.value()
}
pub fn clear(&mut self) -> bool {
self.text.clear() || self.choice.clear()
}
pub fn select(&mut self, select: usize) -> bool {
if self.choice.select(select) {
self.text.set_value(self.choice.value());
true
} else {
false
}
}
pub fn selected(&self) -> Option<usize> {
self.choice.selected()
}
pub fn is_empty(&self) -> bool {
self.choice.is_empty()
}
pub fn len(&self) -> usize {
self.choice.len()
}
pub fn clear_offset(&mut self) {
self.choice.set_offset(0);
}
pub fn set_offset(&mut self, offset: usize) -> bool {
self.choice.set_offset(offset)
}
pub fn offset(&self) -> usize {
self.choice.offset()
}
pub fn max_offset(&self) -> usize {
self.choice.max_offset()
}
pub fn page_len(&self) -> usize {
self.choice.page_len()
}
pub fn scroll_by(&self) -> usize {
self.choice.scroll_by()
}
pub fn scroll_to_selected(&mut self) -> bool {
self.choice.scroll_to_selected()
}
}
impl ComboboxState {
pub fn select_by_char(&mut self, c: char) -> bool {
if self.choice.select_by_char(c) {
self.text.set_value(self.choice.value());
true
} else {
false
}
}
pub fn reverse_select_by_char(&mut self, c: char) -> bool {
if self.choice.reverse_select_by_char(c) {
self.text.set_value(self.choice.value());
true
} else {
false
}
}
pub fn move_to(&mut self, n: usize) -> ComboboxOutcome {
match self.choice.move_to(n) {
ChoiceOutcome::Continue => ComboboxOutcome::Continue,
ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
ChoiceOutcome::Changed => ComboboxOutcome::Changed,
ChoiceOutcome::Value => {
self.text.set_value(self.choice.value());
ComboboxOutcome::Value
}
}
}
pub fn move_down(&mut self, n: usize) -> ComboboxOutcome {
match self.choice.move_down(n) {
ChoiceOutcome::Continue => ComboboxOutcome::Continue,
ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
ChoiceOutcome::Changed => ComboboxOutcome::Changed,
ChoiceOutcome::Value => {
self.text.set_value(self.choice.value());
ComboboxOutcome::Value
}
}
}
pub fn move_up(&mut self, n: usize) -> ComboboxOutcome {
match self.choice.move_up(n) {
ChoiceOutcome::Continue => ComboboxOutcome::Continue,
ChoiceOutcome::Unchanged => ComboboxOutcome::Unchanged,
ChoiceOutcome::Changed => ComboboxOutcome::Changed,
ChoiceOutcome::Value => {
self.text.set_value(self.choice.value());
ComboboxOutcome::Value
}
}
}
}
impl HandleEvent<Event, Regular, ComboboxOutcome> for ComboboxState {
fn handle(&mut self, event: &Event, _qualifier: Regular) -> ComboboxOutcome {
self.handle(event, Popup)
}
}
impl HandleEvent<Event, Popup, ComboboxOutcome> for ComboboxState {
fn handle(&mut self, event: &Event, _qualifier: Popup) -> ComboboxOutcome {
let r = if self.is_focused() {
match event {
ct_event!(keycode press Enter) => {
self.flip_popup_active();
ComboboxOutcome::Changed
}
ct_event!(keycode press Esc) => {
if self.set_popup_active(false) {
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Continue
}
}
ct_event!(keycode press Down) => self.move_down(1),
ct_event!(keycode press Up) => self.move_up(1),
ct_event!(keycode press PageUp) => self.move_up(self.page_len()),
ct_event!(keycode press PageDown) => self.move_down(self.page_len()),
ct_event!(keycode press ALT-Home) => self.move_to(0),
ct_event!(keycode press ALT-End) => self.move_to(self.len().saturating_sub(1)),
Event::Key(_) => match self.text.handle(event, Regular) {
TextOutcome::Continue => ComboboxOutcome::Continue,
TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
TextOutcome::Changed => ComboboxOutcome::Changed,
TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
},
_ => ComboboxOutcome::Continue,
}
} else {
ComboboxOutcome::Continue
};
if !r.is_consumed() {
self.handle(event, MouseOnly)
} else {
r
}
}
}
impl HandleEvent<Event, MouseOnly, ComboboxOutcome> for ComboboxState {
fn handle(&mut self, event: &Event, _qualifier: MouseOnly) -> ComboboxOutcome {
let r0 = handle_mouse(self, event);
let r1 = handle_select(self, event);
let r2 = handle_close(self, event);
let mut r = max(r0, max(r1, r2));
r = r.or_else(|| match self.text.handle(event, MouseOnly) {
TextOutcome::Continue => ComboboxOutcome::Continue,
TextOutcome::Unchanged => ComboboxOutcome::Unchanged,
TextOutcome::Changed => ComboboxOutcome::Changed,
TextOutcome::TextChanged => ComboboxOutcome::TextChanged,
});
r = r.or_else(|| mouse_trap(event, self.choice.popup.area).into());
r
}
}
fn handle_mouse(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
match event {
ct_event!(mouse down Left for x,y)
| ct_event!(mouse down Right for x,y)
| ct_event!(mouse down Middle for x,y)
if !state.choice.item_area.contains((*x, *y).into())
&& !state.choice.button_area.contains((*x, *y).into()) =>
{
match state.choice.popup.handle(event, Popup) {
PopupOutcome::Hide => {
state.set_popup_active(false);
return ComboboxOutcome::Changed;
}
_ => {}
}
}
_ => {}
}
if !state.has_mouse_focus() {
return ComboboxOutcome::Continue;
}
match event {
ct_event!(mouse down Left for x,y)
if state.choice.button_area.contains((*x, *y).into()) =>
{
if !state.gained_focus() {
state.flip_popup_active();
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Continue
}
}
_ => ComboboxOutcome::Continue,
}
}
fn handle_select(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
if !state.has_mouse_focus() {
return ComboboxOutcome::Continue;
}
match state.choice.behave_select {
ChoiceSelect::MouseScroll => {
let mut sas = ScrollAreaState::new()
.area(state.choice.popup.area)
.v_scroll(&mut state.choice.popup_scroll);
let mut r = match sas.handle(event, MouseOnly) {
ScrollOutcome::Up(n) => state.move_up(n),
ScrollOutcome::Down(n) => state.move_down(n),
ScrollOutcome::VPos(n) => state.move_to(n),
_ => ComboboxOutcome::Continue,
};
r = r.or_else(|| match event {
ct_event!(mouse down Left for x,y)
if state.choice.popup.area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
state.move_to(state.offset() + n)
} else {
ComboboxOutcome::Unchanged
}
}
ct_event!(mouse drag Left for x,y)
if state.choice.popup.area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
state.move_to(state.offset() + n)
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
});
r
}
ChoiceSelect::MouseMove => {
let mut r = if let Some(selected) = state.choice.core.selected() {
let rel_sel = selected.saturating_sub(state.offset());
let mut sas = ScrollAreaState::new()
.area(state.choice.popup.area)
.v_scroll(&mut state.choice.popup_scroll);
match sas.handle(event, MouseOnly) {
ScrollOutcome::Up(n) => {
state.choice.popup_scroll.scroll_up(n);
if state.select(state.offset() + rel_sel) {
ComboboxOutcome::Value
} else {
ComboboxOutcome::Unchanged
}
}
ScrollOutcome::Down(n) => {
state.choice.popup_scroll.scroll_down(n);
if state.select(state.offset() + rel_sel) {
ComboboxOutcome::Value
} else {
ComboboxOutcome::Unchanged
}
}
ScrollOutcome::VPos(n) => {
if state.choice.popup_scroll.set_offset(n) {
ComboboxOutcome::Value
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
}
} else {
ComboboxOutcome::Continue
};
r = r.or_else(|| match event {
ct_event!(mouse moved for x,y)
if state.choice.popup.area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
state.move_to(state.offset() + n)
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
});
r
}
ChoiceSelect::MouseClick => {
let mut sas = ScrollAreaState::new()
.area(state.choice.popup.area)
.v_scroll(&mut state.choice.popup_scroll);
let mut r = match sas.handle(event, MouseOnly) {
ScrollOutcome::Up(n) => {
if state.choice.popup_scroll.scroll_up(n) {
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Unchanged
}
}
ScrollOutcome::Down(n) => {
if state.choice.popup_scroll.scroll_down(n) {
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Unchanged
}
}
ScrollOutcome::VPos(n) => {
if state.choice.popup_scroll.set_offset(n) {
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
};
r = r.or_else(|| match event {
ct_event!(mouse down Left for x,y)
if state.choice.popup.area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
state.move_to(state.offset() + n)
} else {
ComboboxOutcome::Unchanged
}
}
ct_event!(mouse drag Left for x,y)
if state.choice.popup.area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
state.move_to(state.offset() + n)
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
});
r
}
}
}
fn handle_close(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
if !state.has_mouse_focus() {
return ComboboxOutcome::Continue;
}
match state.choice.behave_close {
ChoiceClose::SingleClick => match event {
ct_event!(mouse down Left for x,y)
if state.choice.popup.area.contains((*x, *y).into()) =>
{
if let Some(n) = item_at(&state.choice.item_areas, *x, *y) {
let r = state.move_to(state.offset() + n);
let s = if state.set_popup_active(false) {
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Unchanged
};
max(r, s)
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
},
ChoiceClose::DoubleClick => match event {
ct_event!(mouse any for m) if state.mouse.doubleclick(state.choice.popup.area, m) => {
if let Some(n) = item_at(&state.choice.item_areas, m.column, m.row) {
let r = state.move_to(state.offset() + n);
let s = if state.set_popup_active(false) {
ComboboxOutcome::Changed
} else {
ComboboxOutcome::Unchanged
};
max(r, s)
} else {
ComboboxOutcome::Unchanged
}
}
_ => ComboboxOutcome::Continue,
},
}
}
pub fn handle_events(state: &mut ComboboxState, focus: bool, event: &Event) -> ComboboxOutcome {
state.focus.set(focus);
HandleEvent::handle(state, event, Popup)
}
pub fn handle_mouse_events(state: &mut ComboboxState, event: &Event) -> ComboboxOutcome {
HandleEvent::handle(state, event, MouseOnly)
}