1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-30 13:20:05 -04:00

Context menus (#543)

Main usage: `response.context_menu(…)` and `ui.menu_button`
This commit is contained in:
Linus Behrbohm
2021-10-26 19:55:42 +02:00
committed by GitHub
parent b31ca7efc9
commit 46fb9ff09b
10 changed files with 792 additions and 74 deletions

View File

@@ -0,0 +1,174 @@
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
enum Plot {
Sin,
Bell,
Sigmoid,
}
fn gaussian(x: f64) -> f64 {
let var: f64 = 2.0;
f64::exp(-(x / var).powi(2)) / (var * f64::sqrt(std::f64::consts::TAU))
}
fn sigmoid(x: f64) -> f64 {
-1.0 + 2.0 / (1.0 + f64::exp(-x))
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct ContextMenus {
title: String,
plot: Plot,
show_axes: [bool; 2],
allow_drag: bool,
allow_zoom: bool,
center_x_axis: bool,
center_y_axis: bool,
width: f32,
height: f32,
}
impl ContextMenus {
fn example_plot(&self) -> egui::plot::Plot {
use egui::plot::{Line, Value, Values};
let n = 128;
let line = Line::new(Values::from_values_iter((0..=n).map(|i| {
use std::f64::consts::TAU;
let x = egui::remap(i as f64, 0.0..=n as f64, -TAU..=TAU);
match self.plot {
Plot::Sin => Value::new(x, x.sin()),
Plot::Bell => Value::new(x, 10.0 * gaussian(x)),
Plot::Sigmoid => Value::new(x, sigmoid(x)),
}
})));
egui::plot::Plot::new("example_plot")
.show_axes(self.show_axes)
.allow_drag(self.allow_drag)
.allow_zoom(self.allow_zoom)
.center_x_axis(self.center_x_axis)
.center_x_axis(self.center_y_axis)
.line(line)
.width(self.width)
.height(self.height)
.data_aspect(1.0)
}
fn nested_menus(ui: &mut egui::Ui) {
if ui.button("Open...").clicked() {
ui.close_menu();
}
ui.menu_button("SubMenu", |ui| {
ui.menu_button("SubMenu", |ui| {
if ui.button("Open...").clicked() {
ui.close_menu();
}
let _ = ui.button("Item");
});
ui.menu_button("SubMenu", |ui| {
if ui.button("Open...").clicked() {
ui.close_menu();
}
let _ = ui.button("Item");
});
let _ = ui.button("Item");
if ui.button("Open...").clicked() {
ui.close_menu();
}
});
ui.menu_button("SubMenu", |ui| {
let _ = ui.button("Item1");
let _ = ui.button("Item2");
let _ = ui.button("Item3");
let _ = ui.button("Item4");
if ui.button("Open...").clicked() {
ui.close_menu();
}
});
let _ = ui.button("Very long text for this item");
}
}
const DEFAULT_TITLE: &str = "☰ Context Menus";
impl Default for ContextMenus {
fn default() -> Self {
Self {
title: DEFAULT_TITLE.to_owned(),
plot: Plot::Sin,
show_axes: [true, true],
allow_drag: true,
allow_zoom: true,
center_x_axis: false,
center_y_axis: false,
width: 400.0,
height: 200.0,
}
}
}
impl super::Demo for ContextMenus {
fn name(&self) -> &'static str {
DEFAULT_TITLE
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
let Self { title, .. } = self.clone();
use super::View;
let window = egui::Window::new(title)
.id(egui::Id::new("demo_context_menus")) // required since we change the title
.vscroll(false)
.open(open);
window.show(ctx, |ui| self.ui(ui));
}
}
impl super::View for ContextMenus {
fn ui(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| ui.text_edit_singleline(&mut self.title));
ui.horizontal(|ui| {
ui.add(self.example_plot())
.on_hover_text("Right click for options")
.context_menu(|ui| {
ui.menu_button("Plot", |ui| {
if ui.radio_value(&mut self.plot, Plot::Sin, "Sin").clicked()
|| ui
.radio_value(&mut self.plot, Plot::Bell, "Gaussian")
.clicked()
|| ui
.radio_value(&mut self.plot, Plot::Sigmoid, "Sigmoid")
.clicked()
{
ui.close_menu();
}
});
egui::Grid::new("button_grid").show(ui, |ui| {
ui.add(
egui::DragValue::new(&mut self.width)
.speed(1.0)
.prefix("Width:"),
);
ui.add(
egui::DragValue::new(&mut self.height)
.speed(1.0)
.prefix("Height:"),
);
ui.end_row();
ui.checkbox(&mut self.show_axes[0], "x-Axis");
ui.checkbox(&mut self.show_axes[1], "y-Axis");
ui.end_row();
if ui.checkbox(&mut self.allow_drag, "Drag").changed()
|| ui.checkbox(&mut self.allow_zoom, "Zoom").changed()
{
ui.close_menu();
}
});
});
});
ui.label("Right-click plot to edit it!");
ui.separator();
ui.horizontal(|ui| {
ui.menu_button("Click for menu", Self::nested_menus);
ui.button("Right-click for menu")
.context_menu(Self::nested_menus);
});
}
}

View File

@@ -18,6 +18,7 @@ impl Default for Demos {
Self::from_demos(vec![
Box::new(super::code_editor::CodeEditor::default()),
Box::new(super::code_example::CodeExample::default()),
Box::new(super::context_menu::ContextMenus::default()),
Box::new(super::dancing_strings::DancingStrings::default()),
Box::new(super::drag_and_drop::DragAndDropDemo::default()),
Box::new(super::font_book::FontBook::default()),
@@ -227,9 +228,10 @@ fn show_menu_bar(ui: &mut Ui) {
use egui::*;
menu::bar(ui, |ui| {
menu::menu(ui, "File", |ui| {
ui.menu_button("File", |ui| {
if ui.button("Organize windows").clicked() {
ui.ctx().memory().reset_areas();
ui.close_menu();
}
if ui
.button("Reset egui memory")
@@ -237,6 +239,7 @@ fn show_menu_bar(ui: &mut Ui) {
.clicked()
{
*ui.ctx().memory() = Default::default();
ui.close_menu();
}
});
});

View File

@@ -75,12 +75,12 @@ pub fn drop_target<R>(
InnerResponse::new(ret, response)
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct DragAndDropDemo {
/// columns with items
columns: Vec<Vec<&'static str>>,
columns: Vec<Vec<String>>,
}
impl Default for DragAndDropDemo {
fn default() -> Self {
Self {
@@ -88,7 +88,10 @@ impl Default for DragAndDropDemo {
vec!["Item A", "Item B", "Item C"],
vec!["Item D", "Item E"],
vec!["Item F", "Item G", "Item H"],
],
]
.into_iter()
.map(|v| v.into_iter().map(ToString::to_string).collect())
.collect(),
}
}
}
@@ -114,20 +117,25 @@ impl super::View for DragAndDropDemo {
ui.label("This is a proof-of-concept of drag-and-drop in egui.");
ui.label("Drag items between columns.");
let id_source = "my_drag_and_drop_demo";
let mut source_col_row = None;
let mut drop_col = None;
ui.columns(self.columns.len(), |uis| {
for (col_idx, column) in self.columns.iter().enumerate() {
for (col_idx, column) in self.columns.clone().into_iter().enumerate() {
let ui = &mut uis[col_idx];
let can_accept_what_is_being_dragged = true; // We accept anything being dragged (for now) ¯\_(ツ)_/¯
let response = drop_target(ui, can_accept_what_is_being_dragged, |ui| {
ui.set_min_size(vec2(64.0, 100.0));
for (row_idx, &item) in column.iter().enumerate() {
let item_id = Id::new("item").with(col_idx).with(row_idx);
for (row_idx, item) in column.iter().enumerate() {
let item_id = Id::new(id_source).with(col_idx).with(row_idx);
drag_source(ui, item_id, |ui| {
ui.label(item);
let response = ui.add(Label::new(item).sense(Sense::click()));
response.context_menu(|ui| {
if ui.button("Remove").clicked() {
self.columns[col_idx].remove(row_idx);
ui.close_menu();
}
});
});
if ui.memory().is_being_dragged(item_id) {
@@ -137,6 +145,13 @@ impl super::View for DragAndDropDemo {
})
.response;
response.context_menu(|ui| {
if ui.button("New Item").clicked() {
self.columns[col_idx].push("New Item".to_string());
ui.close_menu();
}
});
let is_being_dragged = ui.memory().is_anything_being_dragged();
if is_being_dragged && can_accept_what_is_being_dragged && response.hovered() {
drop_col = Some(col_idx);

View File

@@ -7,6 +7,7 @@
mod app;
pub mod code_editor;
pub mod code_example;
pub mod context_menu;
pub mod dancing_strings;
pub mod demo_app_windows;
pub mod drag_and_drop;

View File

@@ -212,6 +212,7 @@ impl WidgetGallery {
ui.add(doc_link_label("Plot", "plot"));
ui.add(example_plot());
ui.end_row();
ui.hyperlink_to(
@@ -227,14 +228,14 @@ impl WidgetGallery {
}
fn example_plot() -> egui::plot::Plot {
use egui::plot::{Line, Plot, Value, Values};
use egui::plot::{Line, Value, Values};
let n = 128;
let line = Line::new(Values::from_values_iter((0..=n).map(|i| {
use std::f64::consts::TAU;
let x = egui::remap(i as f64, 0.0..=(n as f64), -TAU..=TAU);
let x = egui::remap(i as f64, 0.0..=n as f64, -TAU..=TAU);
Value::new(x, x.sin())
})));
Plot::new("example_plot")
egui::plot::Plot::new("example_plot")
.line(line)
.height(32.0)
.data_aspect(1.0)

View File

@@ -87,7 +87,6 @@ impl super::View for WindowOptions {
anchor,
anchor_offset,
} = self;
ui.horizontal(|ui| {
ui.label("title:");
ui.text_edit_singleline(title);