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

Add Popup and Tooltip, unifying the previous behaviours #5713

Merged
merged 30 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
254e444
Add `Popup` and `Tooltip`, unifying the previous behaviours
lucasmerlin Feb 12, 2025
199db96
Small fixes
lucasmerlin Feb 12, 2025
d977cee
Mark all deprecated
lucasmerlin Feb 12, 2025
315e2ae
Smart popup positioning based on available space
lucasmerlin Feb 12, 2025
915a4ab
PositionAlign docs
lucasmerlin Feb 12, 2025
be1b0f8
Remove old tooltip positioning
lucasmerlin Feb 12, 2025
37be3f0
Cleanup
lucasmerlin Feb 12, 2025
ba81bb5
Allow specifying fallback positions and implement combobox with new p…
lucasmerlin Feb 12, 2025
acdd41d
Update popup example
lucasmerlin Feb 12, 2025
d32a0ac
Fixes and improvements
lucasmerlin Feb 12, 2025
df33197
Remove some uncommented code and todos
lucasmerlin Feb 12, 2025
fb64785
Base PositionAlign on two Align2 and rename to Align4
lucasmerlin Feb 13, 2025
efedb6a
Remember the pointer position for context menus
lucasmerlin Feb 13, 2025
47a50bd
Clippy
lucasmerlin Feb 13, 2025
cb2ca4e
Move Align4 to emath
lucasmerlin Feb 13, 2025
b31571e
Small improvements
lucasmerlin Feb 13, 2025
76d4db6
Allow passing layout to area, popup and tooltip
lucasmerlin Feb 13, 2025
36a0c0f
Resolve todos
lucasmerlin Feb 13, 2025
fc9821e
Lints
lucasmerlin Feb 13, 2025
5ce3ed1
Update snapshot
lucasmerlin Feb 13, 2025
bc5a750
Fix doc tests
lucasmerlin Feb 13, 2025
575d580
Rename Align4 to RectRelation and pivot and focus to parent and child
lucasmerlin Feb 18, 2025
c3f150f
Move to own file
lucasmerlin Feb 18, 2025
a9dd2f3
Docs
lucasmerlin Feb 18, 2025
d3a65b9
Remove todo
lucasmerlin Feb 18, 2025
740e56c
More docs, rename rect_relation to rect_align, store ctx in Popup
lucasmerlin Feb 18, 2025
54f4a7b
Rename gap vector to gap factor
lucasmerlin Feb 18, 2025
5ae1975
Fixes
lucasmerlin Feb 18, 2025
b922cf6
Fixes
lucasmerlin Feb 18, 2025
f3d1ad8
Fixes
lucasmerlin Feb 18, 2025
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
19 changes: 16 additions & 3 deletions crates/egui/src/containers/area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
use emath::GuiRounding as _;

use crate::{
emath, pos2, Align2, Context, Id, InnerResponse, LayerId, NumExt, Order, Pos2, Rect, Response,
Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState,
emath, pos2, Align2, Context, Id, InnerResponse, LayerId, Layout, NumExt, Order, Pos2, Rect,
Response, Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState,
};

/// State of an [`Area`] that is persisted between frames.
Expand Down Expand Up @@ -120,6 +120,7 @@ pub struct Area {
anchor: Option<(Align2, Vec2)>,
new_pos: Option<Pos2>,
fade_in: bool,
layout: Layout,
}

impl WidgetWithState for Area {
Expand All @@ -145,6 +146,7 @@ impl Area {
pivot: Align2::LEFT_TOP,
anchor: None,
fade_in: true,
layout: Layout::default(),
}
}

Expand Down Expand Up @@ -339,6 +341,13 @@ impl Area {
self.fade_in = fade_in;
self
}

/// Set the layout for the child Ui.
#[inline]
pub fn layout(mut self, layout: Layout) -> Self {
self.layout = layout;
self
}
}

pub(crate) struct Prepared {
Expand All @@ -358,6 +367,7 @@ pub(crate) struct Prepared {
sizing_pass: bool,

fade_in: bool,
layout: Layout,
}

impl Area {
Expand Down Expand Up @@ -390,6 +400,7 @@ impl Area {
constrain,
constrain_rect,
fade_in,
layout,
} = self;

let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect());
Expand Down Expand Up @@ -516,6 +527,7 @@ impl Area {
constrain_rect,
sizing_pass,
fade_in,
layout,
}
}
}
Expand Down Expand Up @@ -543,7 +555,8 @@ impl Prepared {
let mut ui_builder = UiBuilder::new()
.ui_stack_info(UiStackInfo::new(self.kind))
.layer_id(self.layer_id)
.max_rect(max_rect);
.max_rect(max_rect)
.layout(self.layout);

if !self.enabled {
ui_builder = ui_builder.disabled();
Expand Down
98 changes: 24 additions & 74 deletions crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
use epaint::Shape;

use crate::{
epaint, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse, NumExt, Painter,
epaint, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse, NumExt, Painter, Popup,
PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle, TextWrapMode, Ui,
UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType,
};

#[allow(unused_imports)] // Documentation
use crate::style::Spacing;

/// Indicate whether a popup will be shown above or below the box.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AboveOrBelow {
Above,
Below,
}

/// A function that paints the [`ComboBox`] icon
pub type IconPainter = Box<dyn FnOnce(&Ui, Rect, &WidgetVisuals, bool, AboveOrBelow)>;
pub type IconPainter = Box<dyn FnOnce(&Ui, Rect, &WidgetVisuals, bool)>;

/// A drop-down selection menu with a descriptive label.
///
Expand Down Expand Up @@ -135,7 +128,6 @@ impl ComboBox {
/// rect: egui::Rect,
/// visuals: &egui::style::WidgetVisuals,
/// _is_open: bool,
/// _above_or_below: egui::AboveOrBelow,
/// ) {
/// let rect = egui::Rect::from_center_size(
/// rect.center(),
Expand All @@ -154,10 +146,8 @@ impl ComboBox {
/// .show_ui(ui, |_ui| {});
/// # });
/// ```
pub fn icon(
mut self,
icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool, AboveOrBelow) + 'static,
) -> Self {
#[inline]
pub fn icon(mut self, icon_fn: impl FnOnce(&Ui, Rect, &WidgetVisuals, bool) + 'static) -> Self {
self.icon = Some(Box::new(icon_fn));
self
}
Expand Down Expand Up @@ -322,22 +312,6 @@ fn combo_box_dyn<'c, R>(

let is_popup_open = ui.memory(|m| m.is_popup_open(popup_id));

let popup_height = ui.memory(|m| {
m.areas()
.get(popup_id)
.and_then(|state| state.size)
.map_or(100.0, |size| size.y)
});

let above_or_below =
if ui.next_widget_position().y + ui.spacing().interact_size.y + popup_height
< ui.ctx().screen_rect().bottom()
{
AboveOrBelow::Below
} else {
AboveOrBelow::Above
};

let wrap_mode = wrap_mode.unwrap_or_else(|| ui.wrap_mode());

let close_behavior = close_behavior.unwrap_or(PopupCloseBehavior::CloseOnClick);
Expand Down Expand Up @@ -385,15 +359,9 @@ fn combo_box_dyn<'c, R>(
icon_rect.expand(visuals.expansion),
visuals,
is_popup_open,
above_or_below,
);
} else {
paint_default_icon(
ui.painter(),
icon_rect.expand(visuals.expansion),
visuals,
above_or_below,
);
paint_default_icon(ui.painter(), icon_rect.expand(visuals.expansion), visuals);
}

let text_rect = Align2::LEFT_CENTER.align_size_within_rect(galley.size(), rect);
Expand All @@ -402,19 +370,15 @@ fn combo_box_dyn<'c, R>(
}
});

if button_response.clicked() {
ui.memory_mut(|mem| mem.toggle_popup(popup_id));
}

let height = height.unwrap_or_else(|| ui.spacing().combo_height);

let inner = crate::popup::popup_above_or_below_widget(
ui,
popup_id,
&button_response,
above_or_below,
close_behavior,
|ui| {
let inner = Popup::menu(&button_response)
.id(popup_id)
.width(button_response.rect.width())
.close_behavior(close_behavior)
.show(ui.ctx(), |ui| {
ui.set_min_width(ui.available_width());

ScrollArea::vertical()
.max_height(height)
.show(ui, |ui| {
Expand All @@ -427,8 +391,8 @@ fn combo_box_dyn<'c, R>(
menu_contents(ui)
})
.inner
},
);
})
.map(|r| r.inner);

InnerResponse {
inner,
Expand Down Expand Up @@ -484,33 +448,19 @@ fn button_frame(
response
}

fn paint_default_icon(
painter: &Painter,
rect: Rect,
visuals: &WidgetVisuals,
above_or_below: AboveOrBelow,
) {
fn paint_default_icon(painter: &Painter, rect: Rect, visuals: &WidgetVisuals) {
let rect = Rect::from_center_size(
rect.center(),
vec2(rect.width() * 0.7, rect.height() * 0.45),
);

match above_or_below {
AboveOrBelow::Above => {
// Upward pointing triangle
painter.add(Shape::convex_polygon(
vec![rect.left_bottom(), rect.right_bottom(), rect.center_top()],
visuals.fg_stroke.color,
Stroke::NONE,
));
}
AboveOrBelow::Below => {
// Downward pointing triangle
painter.add(Shape::convex_polygon(
vec![rect.left_top(), rect.right_top(), rect.center_bottom()],
visuals.fg_stroke.color,
Stroke::NONE,
));
}
}
// Downward pointing triangle
// Previously, we would show an up arrow when we expected the popup to open upwards
// (due to lack of space below the button), but this could look weird in edge cases, so this
// feature was removed. (See https://github.com/emilk/egui/pull/5713#issuecomment-2654420245)
painter.add(Shape::convex_polygon(
vec![rect.left_top(), rect.right_top(), rect.center_bottom()],
visuals.fg_stroke.color,
Stroke::NONE,
));
}
6 changes: 5 additions & 1 deletion crates/egui/src/containers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ pub mod collapsing_header;
mod combo_box;
pub mod frame;
pub mod modal;
pub mod old_popup;
pub mod panel;
pub mod popup;
mod popup;
pub(crate) mod resize;
mod scene;
pub mod scroll_area;
mod sides;
mod tooltip;
pub(crate) mod window;

pub use {
Expand All @@ -21,11 +23,13 @@ pub use {
combo_box::*,
frame::Frame,
modal::{Modal, ModalResponse},
old_popup::*,
panel::{CentralPanel, SidePanel, TopBottomPanel},
popup::*,
resize::Resize,
scene::Scene,
scroll_area::ScrollArea,
sides::Sides,
tooltip::*,
window::Window,
};
Loading
Loading