Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context menu #543

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
01aea23
Only drag with primary pointer button
mankinskin Jul 6, 2021
04e52be
Add ContextMenuSystem for context menu
mankinskin Jul 3, 2021
432ac9c
Syntax simplification in demo context menu builder
mankinskin Jul 3, 2021
185d86e
Store ContextMenuSystem in Context and respect Ui clicked
mankinskin Jul 6, 2021
69a33d8
Add context menu for widget_gallery plot
mankinskin Jul 6, 2021
28ef15d
Fix menu being reset when clicking on it
mankinskin Jul 6, 2021
1817b62
Adapt to removal of From conversions Vec2 <-> Pos2
mankinskin Jul 6, 2021
7011c37
Apply context menu to Response
mankinskin Jul 7, 2021
3519508
Rename context_menu functions
mankinskin Jul 7, 2021
aa91812
Refactor
mankinskin Jul 7, 2021
1da8097
Fix warnings
mankinskin Jul 7, 2021
171a818
Use response.clicked to detect clicks
mankinskin Jul 8, 2021
a3f0c5e
Make MenuState::get_submenu private
mankinskin Jul 8, 2021
3f49d85
Improve submenu navigation by allowing crossing other items
mankinskin Jul 8, 2021
2410c76
Refactor and comment
mankinskin Jul 8, 2021
968233d
Prevent breaking of drag on attached elements
mankinskin Jul 8, 2021
c40c120
Close submenu when pointer outside is not moving towards it
mankinskin Jul 8, 2021
ea27d7c
Draw context menu using same Ui as egui::menu
mankinskin Jul 8, 2021
0698b2b
Improve submenu style
mankinskin Jul 8, 2021
3e0e7aa
Fix submenu button flicker
mankinskin Jul 8, 2021
f25e02f
Better encapsulation of MenuState
mankinskin Jul 8, 2021
5e96b56
Remove Clone implementations
mankinskin Jul 8, 2021
8c46041
Some styling improvements
mankinskin Jul 8, 2021
31e9fd4
Fix submenu text alignment
mankinskin Jul 9, 2021
2a1ac41
Refactor, run cargo fmt
mankinskin Jul 9, 2021
ccbd1d2
Add function for adding items to submenus
mankinskin Jul 23, 2021
dee2611
Keep track of submenu index in parent context menu
mankinskin Jul 23, 2021
1697958
Refactoring
mankinskin Jul 23, 2021
ae95f5f
Add button padding to context menu
mankinskin Jul 23, 2021
97c957a
Don't return any Response from SubMenu::show
mankinskin Jul 23, 2021
85d63b1
Define useful Response for SubMenu::show
mankinskin Jul 24, 2021
1238467
Open ContextMenu on pointer button press, not click
mankinskin Jul 25, 2021
3ed068a
Refactoring: Apply PR Review
mankinskin Aug 27, 2021
557184f
cargo clippy
mankinskin Aug 27, 2021
5a31048
Add MenuUi and demo window
mankinskin Aug 27, 2021
2f573a6
Remove MenuUi and manage MenuState in Ui
mankinskin Aug 29, 2021
b8fe011
Use vscroll() instead of scroll()
mankinskin Aug 29, 2021
aef287b
submenus for normal egui:::menus
mankinskin Aug 29, 2021
88d3900
Fix stationary menus not closing when clicking elsewhere
mankinskin Aug 29, 2021
9f3af74
Rebase on emilk/egui@master
mankinskin Sep 29, 2021
700b166
Fix stationary menu rect not being set
mankinskin Sep 29, 2021
37ac070
Apply review
mankinskin Sep 29, 2021
ffbf96f
Fix stationary menus not reacting to close_menu()
mankinskin Sep 29, 2021
a74dff7
cargo fmt & clippy
mankinskin Sep 29, 2021
b54233c
Delete context_menu module
mankinskin Sep 29, 2021
3db6bdf
Run cargo clippy and cargo fmt
mankinskin Oct 25, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add function for adding items to submenus
this allows us to disable hover visuals when any
other entry is hovered
  • Loading branch information
mankinskin committed Oct 25, 2021
commit ccbd1d25019287bdfd0d2537bf6c263ce3d12eef
160 changes: 129 additions & 31 deletions egui/src/context_menu.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use super::{
style::Spacing, CtxRef, Frame, Id, Label, Layout, PointerState, Pos2, Rect, Response, Sense,
style::{
Spacing,
WidgetVisuals,
}, CtxRef, Align, Id, PointerState, Pos2, Rect, Response, Sense,
Style, TextStyle, Ui, Vec2,
};

Expand Down Expand Up @@ -103,48 +106,132 @@ impl std::ops::DerefMut for ContextMenuRoot {
}
}

pub struct SubMenu<'a> {
pub enum EntryState {
/// will not show hover visuals
Inactive,
/// listening for hovers
Active,
/// show open visuals
Open,
}
impl EntryState {
fn visuals<'a>(self, ui: &'a Ui, response: &'_ Response) -> &'a WidgetVisuals {
let widgets = &ui.style().visuals.widgets;
match self {
Self::Inactive => &widgets.inactive,
Self::Active => ui.style().interact(response),
Self::Open => &widgets.hovered,
}
}
fn submenu(menu_state: &MenuState, sub_id: Id) -> Self {
if menu_state.is_open(sub_id) {
Self::Open
} else if menu_state.any_open() {
Self::Inactive
} else {
Self::Active
}
}
fn entry(menu_state: &MenuState) -> Self {
if menu_state.any_open() {
Self::Inactive
} else {
Self::Active
}
}
}
pub struct MenuEntry {
text: String,
icon: String,
state: EntryState,
}
impl MenuEntry {
#[allow(clippy::needless_pass_by_value)]
fn new(text: impl ToString, icon: impl ToString, state: EntryState) -> Self {
Self {
text: text.to_string(),
icon: icon.to_string(),
state,
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn icon(mut self, icon: impl ToString) -> Self {
self.icon = icon.to_string();
self
}
fn show_with_state(mut self, ui: &mut Ui, state: EntryState) -> Response {
self.state = state;
self.show(ui)
}
pub fn show(self, ui: &mut Ui) -> Response {
let MenuEntry {
text,
icon,
state
} = self;

let text_style = TextStyle::Button;
let sense = Sense::click();

let button_padding = ui.spacing().button_padding;
let total_extra = button_padding + button_padding;
let text_available_width = ui.available_width() - total_extra.x;
let text_galley = ui.fonts()
.layout_multiline(text_style, text, text_available_width);

let icon_available_width = text_available_width - text_galley.size.x;
let icon_galley = ui.fonts()
.layout_multiline(text_style, icon, icon_available_width);
let text_and_icon_size = Vec2::new(
text_galley.size.x + icon_galley.size.x,
text_galley.size.y.max(icon_galley.size.y)
);
let desired_size = text_and_icon_size + 2.0 * button_padding;

let (rect, response) = ui.allocate_at_least(desired_size, sense);
response.widget_info(|| crate::WidgetInfo::labeled(crate::WidgetType::Button, &text_galley.text));

if ui.clip_rect().intersects(rect) {
response.interact(sense);
let visuals = state.visuals(ui, &response);
let text_pos = ui.layout()
.align_size_within_rect(text_galley.size, rect.shrink2(button_padding))
.min;
let icon_pos = ui.layout().with_cross_align(Align::RIGHT)
.align_size_within_rect(icon_galley.size, rect.shrink2(button_padding))
.min;

let fill = visuals.bg_fill;
let stroke = crate::Stroke::none();
ui.painter().rect(
rect.expand(visuals.expansion),
visuals.corner_radius,
fill,
stroke,
);

let text_color = visuals.text_color();
ui.painter().galley(text_pos, text_galley, text_color);
ui.painter().galley(icon_pos, icon_galley, text_color);
}
response
}
}
pub struct SubMenu<'a> {
entry: MenuEntry,
parent_state: &'a mut MenuState,
}
impl<'a> SubMenu<'a> {
#[allow(clippy::needless_pass_by_value)]
fn new(text: impl ToString, parent_state: &'a mut MenuState) -> Self {
Self {
text: text.to_string(),
entry: MenuEntry::new(text, "⏵", EntryState::Active),
parent_state,
}
}
pub fn show(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui, &mut MenuState)) -> Response {
let sub_id = ui.id().with(format!("{:?}", ui.placer.cursor().min));
let mut label = Label::new(self.text)
.text_style(TextStyle::Button)
.text_color(ui.visuals().widgets.inactive.fg_stroke.color);
let mut icon = Label::new("⏵")
.text_style(TextStyle::Button)
.text_color(ui.visuals().widgets.inactive.fg_stroke.color);
let mut frame = Frame::none();
let open = self.parent_state.is_open(sub_id);
if open {
icon = icon.text_color(ui.visuals().widgets.hovered.fg_stroke.color);
label = label.text_color(ui.visuals().widgets.hovered.fg_stroke.color);
frame = frame.fill(ui.visuals().widgets.hovered.bg_fill);
}
let padding = ui.spacing().button_padding.x;
let button = frame
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.with_layout(Layout::left_to_right(), |ui| {
ui.add_space(padding);
ui.label(label);
});
ui.with_layout(Layout::right_to_left(), |ui| {
ui.add(icon);
ui.add_space(padding);
});
});
})
.response;
let button = self.entry.show_with_state(ui, EntryState::submenu(self.parent_state, sub_id));
self.parent_state
.submenu_button_interaction(ui, sub_id, &button);
self.parent_state
Expand All @@ -163,6 +250,14 @@ impl MenuState {
pub fn close(&mut self) {
self.response = MenuResponse::Close;
}
/// create a menu item
pub fn item(&self, text: impl ToString) -> MenuEntry {
MenuEntry::new(text, "", EntryState::entry(self))
}
/// create a menu item with an icon
mankinskin marked this conversation as resolved.
Show resolved Hide resolved
pub fn item_with_icon(&self, text: impl ToString, icon: impl ToString) -> MenuEntry {
MenuEntry::new(text, icon, EntryState::entry(self))
}
/// create a sub-menu
pub fn submenu(&'_ mut self, text: impl ToString) -> SubMenu<'_> {
SubMenu::new(text, self)
Expand Down Expand Up @@ -269,6 +364,9 @@ impl MenuState {
self.response = response;
}
}
fn any_open(&self) -> bool {
self.get_sub_id().is_some()
}
fn is_open(&self, id: Id) -> bool {
self.get_sub_id() == Some(id)
}
Expand Down
4 changes: 2 additions & 2 deletions egui_demo_lib/src/apps/demo/drag_and_drop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl super::View for DragAndDropDemo {
drag_source(ui, item_id, |ui| {
ui.add(Label::new(item).sense(Sense::click())).context_menu(
|ui, menu_state| {
if ui.button("Remove...").clicked() {
if menu_state.item("Remove...").show(ui).clicked() {
mankinskin marked this conversation as resolved.
Show resolved Hide resolved
self.columns[col_idx].remove(row_idx);
menu_state.close();
}
Expand All @@ -147,7 +147,7 @@ impl super::View for DragAndDropDemo {

if col_idx == 0 {
response.context_menu(|ui, menu_state| {
if ui.button("New Item...").clicked() {
if menu_state.item("New Item...").show(ui).clicked() {
mankinskin marked this conversation as resolved.
Show resolved Hide resolved
self.columns[0].push("New Item".to_string());
menu_state.close();
}
Expand Down
6 changes: 3 additions & 3 deletions egui_demo_lib/src/apps/demo/widget_gallery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,13 @@ impl WidgetGallery {

ui.add(doc_link_label("Plot", "plot"));
ui.add(example_plot(plot)).context_menu(|ui, menu_state| {
if ui.button("Sin").clicked() {
if menu_state.item("Sin").show(ui).clicked() {
*plot = Plot::Sin;
menu_state.close();
} else if ui.button("Bell").clicked() {
} else if menu_state.item("Bell").show(ui).clicked() {
*plot = Plot::Bell;
menu_state.close();
} else if ui.button("Sigmoid").clicked() {
} else if menu_state.item("Sigmoid").show(ui).clicked() {
*plot = Plot::Sigmoid;
menu_state.close();
}
Expand Down
2 changes: 1 addition & 1 deletion egui_demo_lib/src/apps/demo/window_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ impl super::View for WindowOptions {
})
.response
.context_menu(|ui, menu_state| {
if ui.button("Clear..").clicked() {
if menu_state.item("Clear..").show(ui).clicked() {
*title = String::new();
menu_state.close();
}
Expand Down
24 changes: 12 additions & 12 deletions egui_demo_lib/src/backend_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,34 +93,34 @@ impl BackendPanel {
}

pub fn context_menu(ui: &mut egui::Ui, menu_state: &mut egui::context_menu::MenuState) {
if ui.button("Open...").clicked() {
if menu_state.item("Open...").show(ui).clicked() {
menu_state.close();
}
menu_state.submenu("SubMenu").show(ui, |ui, menu_state| {
menu_state.submenu("SubMenu").show(ui, |ui, menu_state| {
if ui.button("Open...").clicked() {
menu_state.close();
}
let _ = ui.button("Item");
let _ = menu_state.item("Item").show(ui);
});
menu_state.submenu("SubMenu").show(ui, |ui, menu_state| {
if ui.button("Open...").clicked() {
if menu_state.item("Open...").show(ui).clicked() {
menu_state.close();
}
let _ = ui.button("Item");
let _ = menu_state.item("Item").show(ui);
});
let _ = ui.button("Item");
if ui.button("Open...").clicked() {
let _ = menu_state.item("Item").show(ui);
if menu_state.item("Open...").show(ui).clicked() {
menu_state.close();
}
});
menu_state.submenu("SubMenu").show(ui, |ui, _menu_state| {
let _ = ui.button("Item1");
let _ = ui.button("Item2");
let _ = ui.button("Item3");
let _ = ui.button("Item4");
menu_state.submenu("SubMenu").show(ui, |ui, menu_state| {
let _ = menu_state.item("Item1").show(ui);
let _ = menu_state.item("Item1").show(ui);
let _ = menu_state.item("Item1").show(ui);
let _ = menu_state.item("Item1").show(ui);
});
let _ = ui.button("Very long text for this item");
let _ = menu_state.item("Very long text for this item").show(ui);
}
mankinskin marked this conversation as resolved.
Show resolved Hide resolved

pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut epi::Frame<'_>) {
Expand Down
4 changes: 2 additions & 2 deletions egui_demo_lib/src/wrap_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ impl WrapApp {
});
})
.response
.context_menu(|ui, _menu_state| {
if ui.button("Print something").clicked() {
.context_menu(|ui, menu_state| {
if menu_state.item("Print something").show(ui).clicked() {
println!("something");
}
mankinskin marked this conversation as resolved.
Show resolved Hide resolved
});
Expand Down