Skip to content

Commit

Permalink
Allow passing layout to area, popup and tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmerlin committed Feb 13, 2025
1 parent b31571e commit 76d4db6
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 52 deletions.
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
39 changes: 17 additions & 22 deletions crates/egui/src/containers/combo_box.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use emath::Align;
use epaint::Shape;

use crate::{
epaint, style::WidgetVisuals, vec2, Align2, Context, Id, InnerResponse, Layout, NumExt,
Painter, Popup, PopupCloseBehavior, Rect, Response, ScrollArea, Sense, Stroke, TextStyle,
TextWrapMode, Ui, UiBuilder, Vec2, WidgetInfo, WidgetText, WidgetType,
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
Expand Down Expand Up @@ -377,24 +376,20 @@ fn combo_box_dyn<'c, R>(
.width(button_response.rect.width())
.close_behavior(close_behavior)
.show(ui.ctx(), |ui| {
// TODO: Should Popup have a layout option? Maybe also on Area? Maybe even expose UiBuilder on Area?
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
ui.set_min_width(ui.available_width());

ScrollArea::vertical()
.max_height(height)
.show(ui, |ui| {
// Often the button is very narrow, which means this popup
// is also very narrow. Having wrapping on would therefore
// result in labels that wrap very early.
// Instead, we turn it off by default so that the labels
// expand the width of the menu.
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
menu_contents(ui)
})
.inner
})
.inner
ui.set_min_width(ui.available_width());

ScrollArea::vertical()
.max_height(height)
.show(ui, |ui| {
// Often the button is very narrow, which means this popup
// is also very narrow. Having wrapping on would therefore
// result in labels that wrap very early.
// Instead, we turn it off by default so that the labels
// expand the width of the menu.
ui.style_mut().wrap_mode = Some(TextWrapMode::Extend);
menu_contents(ui)
})
.inner
})
.map(|r| r.inner);

Expand Down
10 changes: 3 additions & 7 deletions crates/egui/src/containers/old_popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,21 +195,17 @@ pub fn popup_above_or_below_widget<R>(
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
let response = Popup::from_response(widget_response)
.layout(Layout::top_down_justified(Align::LEFT))
.open_memory(None, close_behavior)
.id(popup_id)
.position(match above_or_below {
AboveOrBelow::Above => Align4::TOP_START,
AboveOrBelow::Below => Align4::BOTTOM_START,
})
// TODO: Should we expose this as an option? It should probably not be the default
.width(widget_response.rect.width())
.show(parent_ui.ctx(), |ui| {
// TODO: Should we have a layout option? Maybe also on Area? Maybe even expose UiBuilder on Area?
ui.with_layout(Layout::top_down_justified(Align::LEFT), |ui| {
ui.set_min_width(ui.available_width());
add_contents(ui)
})
.inner
ui.set_min_width(ui.available_width());
add_contents(ui)
})?;
Some(response.inner)
}
37 changes: 25 additions & 12 deletions crates/egui/src/containers/popup.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Order, Response, Sense, Ui,
UiKind,
Area, AreaState, Context, Frame, Id, InnerResponse, Key, LayerId, Layout, Order, Response,
Sense, Ui, UiKind,
};
use emath::{vec2, Align4, Pos2, Rect};
use emath::{vec2, Align, Align4, Pos2, Rect};
use std::iter::once;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -123,6 +123,7 @@ pub struct Popup<'a> {
/// Default width passed to the Area
width: Option<f32>,
sense: Sense,
layout: Layout,
}

impl<'a> Popup<'a> {
Expand All @@ -139,6 +140,7 @@ impl<'a> Popup<'a> {
widget_clicked_elsewhere: false,
width: None,
sense: Sense::click(),
layout: Layout::default(),
}
}

Expand Down Expand Up @@ -181,18 +183,21 @@ impl<'a> Popup<'a> {
widget_clicked_elsewhere: response.clicked_elsewhere(),
width: Some(widget_rect.width()),
sense: Sense::click(),
layout: Layout::default(),
}
}

pub fn menu(response: &Response) -> Self {
Self::from_response(response).open_memory(
if response.clicked() {
SetOpen::Toggle
} else {
SetOpen::DoNothing
},
PopupCloseBehavior::CloseOnClick,
)
Self::from_response(response)
.open_memory(
if response.clicked() {
SetOpen::Toggle
} else {
SetOpen::DoNothing
},
PopupCloseBehavior::CloseOnClick,
)
.layout(Layout::top_down_justified(Align::Min))
}

pub fn context_menu(response: &Response) -> Self {
Expand All @@ -201,6 +206,7 @@ impl<'a> Popup<'a> {
response.secondary_clicked().then_some(true),
PopupCloseBehavior::CloseOnClick,
)
.layout(Layout::top_down_justified(Align::Min))
.at_pointer_fixed()
.gap(0.0)
}
Expand Down Expand Up @@ -277,6 +283,11 @@ impl<'a> Popup<'a> {
self
}

pub fn layout(mut self, layout: Layout) -> Self {
self.layout = layout;
self
}

/// The width that will be passed to [`Area::default_width`].
pub fn width(mut self, width: f32) -> Self {
self.width = Some(width);
Expand Down Expand Up @@ -348,6 +359,7 @@ impl<'a> Popup<'a> {
widget_clicked_elsewhere,
width,
sense,
layout,
} = self;

let hover_pos = ctx.pointer_hover_pos();
Expand Down Expand Up @@ -401,7 +413,8 @@ impl<'a> Popup<'a> {
.kind(ui_kind)
.pivot(pivot)
.fixed_pos(anchor)
.sense(sense);
.sense(sense)
.layout(layout);

if let Some(width) = width {
area = area.default_width(width);
Expand Down
20 changes: 16 additions & 4 deletions crates/egui/src/containers/tooltip.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::pass_state::PerWidgetTooltipState;
use crate::{
AreaState, Context, Id, InnerResponse, LayerId, Order, Popup, PopupAnchor, PopupKind, Response,
Sense,
AreaState, Context, Id, InnerResponse, LayerId, Layout, Order, Popup, PopupAnchor, PopupKind,
Response, Sense,
};
use emath::Vec2;

Expand Down Expand Up @@ -71,6 +71,18 @@ impl<'a> Tooltip<'a> {
self
}

/// Set the layout of the tooltip
pub fn layout(mut self, layout: Layout) -> Self {
self.popup = self.popup.layout(layout);
self
}

/// Set the width of the tooltip
pub fn width(mut self, width: f32) -> Self {
self.popup = self.popup.width(width);
self
}

/// Show the tooltip
pub fn show<R>(
self,
Expand Down Expand Up @@ -111,7 +123,7 @@ impl<'a> Tooltip<'a> {
popup = popup.anchor(state.bounding_rect).id(tooltip_area_id);

let response = popup.show(ctx, |ui| {
// By default the text in tooltips aren't selectable.
// By default, the text in tooltips aren't selectable.
// This means that most tooltips aren't interactable,
// which also mean they won't stick around so you can click them.
// Only tooltips that have actual interactive stuff (buttons, links, …)
Expand Down Expand Up @@ -180,7 +192,7 @@ impl<'a> Tooltip<'a> {
.is_some_and(|layer| !layer.open_popups.is_empty())
});
if any_open_popups {
// Hide tooltips if the user opens a popup (menu, combo-box, etc) in the same layer.
// Hide tooltips if the user opens a popup (menu, combo-box, etc.) in the same layer.
return false;
}

Expand Down
11 changes: 7 additions & 4 deletions crates/egui_demo_lib/src/demo/popups.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use egui::{Align2, Align4, ComboBox, Frame, Id, Popup, PopupCloseBehavior, Tooltip, Ui};
use egui::{vec2, Align2, Align4, ComboBox, Frame, Id, Popup, PopupCloseBehavior, Tooltip, Ui};

/// Showcase [`Popup`].
#[derive(Clone, PartialEq)]
Expand Down Expand Up @@ -142,8 +142,11 @@ impl crate::View for PopupsDemo {
ui.checkbox(&mut self.popup_open, "Show popup");

let response = Frame::group(ui.style())
.inner_margin(50.0)
.show(ui, |ui| ui.button("Click, right-click and hover me!"))
.inner_margin(vec2(0.0, 25.0))
.show(ui, |ui| {
ui.vertical_centered(|ui| ui.button("Click, right-click and hover me!"))
.inner
})
.inner;

self.apply_options(Popup::menu(&response).id(Id::new("menu")))
Expand All @@ -168,7 +171,7 @@ impl crate::View for PopupsDemo {
let mut tooltip = Tooltip::for_enabled(&response);
tooltip.popup = self.apply_options(tooltip.popup);
tooltip.show(ui.ctx(), |ui| {
ui.label("Tooltips are popups too!");
ui.label("Tooltips are popups, too!");
});

ui.vertical_centered(|ui| {
Expand Down

0 comments on commit 76d4db6

Please sign in to comment.