Add some support for menu bars

This commit is contained in:
Emil Ernerfeldt 2020-05-10 19:03:36 +02:00
parent a8d943db54
commit 34b4c985a2
3 changed files with 153 additions and 1 deletions

View file

@ -1,6 +1,7 @@
pub mod area;
pub mod collapsing_header;
pub mod frame;
pub mod menu;
pub mod resize;
pub mod scroll_area;
pub mod window;

View file

@ -0,0 +1,140 @@
use crate::{widgets::*, *};
use super::*;
#[derive(Clone, Copy, Debug, serde_derive::Deserialize, serde_derive::Serialize)]
pub struct BarState {
#[serde(skip)]
open_menu: Option<Id>,
#[serde(skip)]
/// When did we open a menu?
open_time: f64,
}
impl Default for BarState {
fn default() -> Self {
Self {
open_menu: None,
open_time: f64::NEG_INFINITY,
}
}
}
pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui)) {
ui.horizontal(|ui| {
Frame::default().show(ui, |ui| {
let mut style = ui.style().clone();
style.button_padding = vec2(2.0, 0.0);
style.interact.inactive.fill_color = None;
style.interact.inactive.outline = None;
style.interact.hovered.fill_color = None;
ui.set_style(style);
// Take full width and fixed height:
ui.expand_to_size(vec2(ui.available_width(), ui.style().menu_bar.height));
add_contents(ui)
})
})
}
/// Construct a top level menu in a menu bar. This would be e.g. "File", "Edit" etc.
pub fn menu(ui: &mut Ui, title: impl Into<String>, add_contents: impl FnOnce(&mut Ui)) {
let title = title.into();
let bar_id = ui.id();
let menu_id = Id::new(&title);
let mut bar_state = ui
.memory()
.menu_bar
.get(&bar_id)
.cloned()
.unwrap_or_default();
let mut button = Button::new(title);
if bar_state.open_menu == Some(menu_id) {
button = button.fill_color(ui.style().interact.active.fill_color);
}
let button_interact = ui.add(button);
interact_with_menu_button(&mut bar_state, ui.input(), menu_id, &button_interact);
if bar_state.open_menu == Some(menu_id) {
let area = Area::new(menu_id)
.order(Order::Foreground)
.fixed_pos(button_interact.rect.left_bottom());
let frame = Frame::menu(ui.style());
let resize = Resize::default().auto_sized();
let menu_interact = area.show(ui.ctx(), |ui| {
frame.show(ui, |ui| {
resize.show(ui, |ui| {
let mut style = ui.style().clone();
style.button_padding = vec2(2.0, 0.0);
style.interact.inactive.fill_color = None;
style.interact.inactive.outline = None;
style.interact.active.corner_radius = 0.0;
style.interact.hovered.corner_radius = 0.0;
style.interact.inactive.corner_radius = 0.0;
ui.set_style(style);
ui.set_align(Align::Justified);
add_contents(ui)
})
})
});
if menu_interact.hovered && ui.input().mouse_released {
bar_state.open_menu = None;
}
}
ui.memory().menu_bar.insert(bar_id, bar_state);
}
fn interact_with_menu_button(
bar_state: &mut BarState,
input: &GuiInput,
menu_id: Id,
button_interact: &GuiResponse,
) {
if button_interact.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_interact.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 {
// A quick click
bar_state.open_menu = Some(menu_id);
bar_state.open_time = input.time;
} else {
// A long hold, then release
bar_state.open_menu = None;
}
}
if button_interact.hovered && bar_state.open_menu.is_some() {
bar_state.open_menu = Some(menu_id);
}
let pressed_escape = input.events.iter().any(|event| {
matches!(
event,
Event::Key {
key: Key::Escape,
pressed: true
}
)
});
if pressed_escape {
bar_state.open_menu = None;
}
}

View file

@ -179,6 +179,8 @@ impl Widget for Hyperlink {
pub struct Button {
text: String,
text_color: Option<Color>,
/// None means default for interact
fill_color: Option<Color>,
}
impl Button {
@ -186,6 +188,7 @@ impl Button {
Self {
text: text.into(),
text_color: None,
fill_color: None,
}
}
@ -193,6 +196,11 @@ impl Button {
self.text_color = Some(text_color);
self
}
pub fn fill_color(mut self, fill_color: Option<Color>) -> Self {
self.fill_color = fill_color;
self
}
}
impl Widget for Button {
@ -207,9 +215,12 @@ impl Widget for Button {
let interact = ui.reserve_space(size, Some(id));
let mut text_cursor = interact.rect.left_center() + vec2(padding.x, -0.5 * text_size.y);
text_cursor.y += 2.0; // TODO: why is this needed?
let fill_color = self
.fill_color
.or(ui.style().interact(&interact).fill_color);
ui.add_paint_cmd(PaintCmd::Rect {
corner_radius: ui.style().interact(&interact).corner_radius,
fill_color: ui.style().interact(&interact).fill_color,
fill_color: fill_color,
outline: ui.style().interact(&interact).outline,
rect: interact.rect,
});