Add some support for menu bars
This commit is contained in:
parent
a8d943db54
commit
34b4c985a2
3 changed files with 153 additions and 1 deletions
|
@ -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;
|
||||
|
|
140
emigui/src/containers/menu.rs
Normal file
140
emigui/src/containers/menu.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue