use crate::{color::TRANSPARENT, paint::Stroke, widgets::*, *};
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct BarState {
#[cfg_attr(feature = "serde", serde(skip))]
open_menu: Option<Id>,
#[cfg_attr(feature = "serde", serde(skip))]
open_time: f64,
}
impl Default for BarState {
fn default() -> Self {
Self {
open_menu: None,
open_time: f64::NEG_INFINITY,
}
}
}
impl BarState {
fn load(ctx: &Context, bar_id: &Id) -> Self {
ctx.memory()
.menu_bar
.get(bar_id)
.cloned()
.unwrap_or_default()
}
fn save(self, ctx: &Context, bar_id: Id) {
ctx.memory().menu_bar.insert(bar_id, self);
}
fn close_menus(ctx: &Context, bar_id: Id) {
let mut bar_state = BarState::load(ctx, &bar_id);
bar_state.open_menu = None;
bar_state.save(ctx, bar_id);
}
}
pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Rect) {
ui.horizontal_centered(|ui| {
Frame::menu_bar(ui.style()).show(ui, |ui| {
let mut style = ui.style().clone();
style.spacing.button_padding = vec2(2.0, 0.0);
style.visuals.widgets.active.bg_stroke = Stroke::none();
style.visuals.widgets.hovered.bg_stroke = Stroke::none();
style.visuals.widgets.inactive.bg_fill = TRANSPARENT;
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
ui.set_style(style);
let height = ui.style().spacing.menu_bar_height;
ui.set_desired_height(height);
ui.expand_to_size(vec2(ui.available().width(), height));
let ret = add_contents(ui);
let clicked_outside = !ui.hovered(ui.rect()) && ui.input().mouse.click;
if clicked_outside || ui.input().key_pressed(Key::Escape) {
let bar_id = ui.id();
BarState::close_menus(ui.ctx(), bar_id);
}
ret
})
})
}
pub fn menu(ui: &mut Ui, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui)) {
menu_impl(ui, title, Box::new(add_contents))
}
fn menu_impl<'c>(
ui: &mut Ui,
title: impl Into<String>,
add_contents: Box<dyn FnOnce(&mut Ui) + 'c>,
) {
let title = title.into();
let bar_id = ui.id();
let menu_id = bar_id.with(&title);
let mut bar_state = BarState::load(ui.ctx(), &bar_id);
let mut button = Button::new(title);
if bar_state.open_menu == Some(menu_id) {
button = button.fill(Some(ui.style().visuals.widgets.active.fg_fill));
}
let button_response = ui.add(button);
interact_with_menu_button(&mut bar_state, ui.input(), menu_id, &button_response);
if bar_state.open_menu == Some(menu_id) {
let area = Area::new(menu_id)
.order(Order::Foreground)
.fixed_pos(button_response.rect.left_bottom());
let frame = Frame::menu(ui.style());
let menu_response = area.show(ui.ctx(), |ui| {
frame.show(ui, |ui| {
let mut style = ui.style().clone();
style.spacing.button_padding = vec2(2.0, 0.0);
style.visuals.widgets.active.bg_stroke = Stroke::none();
style.visuals.widgets.hovered.bg_stroke = Stroke::none();
style.visuals.widgets.inactive.bg_fill = TRANSPARENT;
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
ui.set_style(style);
ui.set_layout(Layout::justified(Direction::Vertical));
add_contents(ui)
})
});
if menu_response.hovered && ui.input().mouse.released {
bar_state.open_menu = None;
}
}
bar_state.save(ui.ctx(), bar_id);
}
fn interact_with_menu_button(
bar_state: &mut BarState,
input: &InputState,
menu_id: Id,
button_response: &Response,
) {
if button_response.hovered && input.mouse.pressed {
if bar_state.open_menu.is_some() {
bar_state.open_menu = None;
} else {
bar_state.open_menu = Some(menu_id);
bar_state.open_time = input.time;
}
}
if button_response.hovered && input.mouse.released && bar_state.open_menu.is_some() {
let time_since_open = input.time - bar_state.open_time;
if time_since_open < 0.4 {
bar_state.open_menu = Some(menu_id);
bar_state.open_time = input.time;
} else {
bar_state.open_menu = None;
}
}
if button_response.hovered && bar_state.open_menu.is_some() {
bar_state.open_menu = Some(menu_id);
}
}