mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Popup-in-popup (draft)
This commit is contained in:
@@ -521,8 +521,8 @@ impl SubMenu {
|
|||||||
// Only edge case is the user hovering this submenu's button, so we also check
|
// Only edge case is the user hovering this submenu's button, so we also check
|
||||||
// if we clicked outside the parent menu (which we luckily have access to here).
|
// if we clicked outside the parent menu (which we luckily have access to here).
|
||||||
let clicked_outside = is_deepest_submenu
|
let clicked_outside = is_deepest_submenu
|
||||||
&& popup_response.response.clicked_elsewhere()
|
&& popup_response.response.clicked_elsewhere_excluding_child_layers()
|
||||||
&& menu_root_response.clicked_elsewhere();
|
&& menu_root_response.clicked_elsewhere_excluding_child_layers();
|
||||||
|
|
||||||
// We never automatically close when a submenu button is clicked, (so menus work
|
// We never automatically close when a submenu button is clicked, (so menus work
|
||||||
// on touch devices)
|
// on touch devices)
|
||||||
|
|||||||
@@ -513,19 +513,19 @@ impl<'a> Popup<'a> {
|
|||||||
if open {
|
if open {
|
||||||
match self.anchor {
|
match self.anchor {
|
||||||
PopupAnchor::PointerFixed => {
|
PopupAnchor::PointerFixed => {
|
||||||
self.ctx.memory_mut(|mem| mem.open_popup_at(id, hover_pos));
|
self.ctx.memory_mut(|mem| mem.open_popup_at(self.layer_id, id, hover_pos));
|
||||||
}
|
}
|
||||||
_ => Popup::open_id(&self.ctx, id),
|
_ => Popup::open_id(&self.ctx, self.layer_id, id),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Self::close_id(&self.ctx, id);
|
Self::close_id(&self.ctx, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(SetOpenCommand::Toggle) => {
|
Some(SetOpenCommand::Toggle) => {
|
||||||
Self::toggle_id(&self.ctx, id);
|
Self::toggle_id(&self.ctx, self.layer_id, id);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.ctx.memory_mut(|mem| mem.keep_popup_open(id));
|
self.ctx.memory_mut(|mem| mem.keep_popup_open(self.layer_id, id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -570,7 +570,7 @@ impl<'a> Popup<'a> {
|
|||||||
let (pivot, anchor) = best_align.pivot_pos(&anchor_rect, gap);
|
let (pivot, anchor) = best_align.pivot_pos(&anchor_rect, gap);
|
||||||
|
|
||||||
let mut area = Area::new(id)
|
let mut area = Area::new(id)
|
||||||
.order(kind.order())
|
.order(layer_id.order)
|
||||||
.pivot(pivot)
|
.pivot(pivot)
|
||||||
.fixed_pos(anchor)
|
.fixed_pos(anchor)
|
||||||
.sense(sense)
|
.sense(sense)
|
||||||
@@ -589,6 +589,7 @@ impl<'a> Popup<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut response = area.show(&ctx, |ui| {
|
let mut response = area.show(&ctx, |ui| {
|
||||||
|
ui.set_sublayer(layer_id, ui.layer_id());
|
||||||
style.apply(ui.style_mut());
|
style.apply(ui.style_mut());
|
||||||
let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
|
let frame = frame.unwrap_or_else(|| Frame::popup(ui.style()));
|
||||||
frame.show(ui, content).inner
|
frame.show(ui, content).inner
|
||||||
@@ -600,7 +601,7 @@ impl<'a> Popup<'a> {
|
|||||||
let closed_by_click = match close_behavior {
|
let closed_by_click = match close_behavior {
|
||||||
PopupCloseBehavior::CloseOnClick => close_click,
|
PopupCloseBehavior::CloseOnClick => close_click,
|
||||||
PopupCloseBehavior::CloseOnClickOutside => {
|
PopupCloseBehavior::CloseOnClickOutside => {
|
||||||
close_click && response.response.clicked_elsewhere()
|
close_click && response.response.clicked_elsewhere_excluding_child_layers()
|
||||||
}
|
}
|
||||||
PopupCloseBehavior::IgnoreClicks => false,
|
PopupCloseBehavior::IgnoreClicks => false,
|
||||||
};
|
};
|
||||||
@@ -670,15 +671,15 @@ impl Popup<'_> {
|
|||||||
/// If you are NOT using [`Popup::show`], you must
|
/// If you are NOT using [`Popup::show`], you must
|
||||||
/// also call [`crate::Memory::keep_popup_open`] as long as
|
/// also call [`crate::Memory::keep_popup_open`] as long as
|
||||||
/// you're showing the popup.
|
/// you're showing the popup.
|
||||||
pub fn open_id(ctx: &Context, popup_id: Id) {
|
pub fn open_id(ctx: &Context, layer_id: LayerId, popup_id: Id) {
|
||||||
ctx.memory_mut(|mem| mem.open_popup(popup_id));
|
ctx.memory_mut(|mem| mem.open_popup(layer_id, popup_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle the given popup between closed and open.
|
/// Toggle the given popup between closed and open.
|
||||||
///
|
///
|
||||||
/// Note: At most, only one popup can be open at a time.
|
/// Note: At most, only one popup can be open at a time.
|
||||||
pub fn toggle_id(ctx: &Context, popup_id: Id) {
|
pub fn toggle_id(ctx: &Context, layer_id: LayerId, popup_id: Id) {
|
||||||
ctx.memory_mut(|mem| mem.toggle_popup(popup_id));
|
ctx.memory_mut(|mem| mem.toggle_popup(layer_id, popup_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close all currently open popups.
|
/// Close all currently open popups.
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use ahash::{HashMap, HashSet};
|
|||||||
use epaint::emath::TSTransform;
|
use epaint::emath::TSTransform;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style, Vec2, ViewportId,
|
area, vec2, EventFilter, Id, IdMap, LayerId, Order, Pos2, Rangef, RawInput, Rect, Style,
|
||||||
ViewportIdMap, ViewportIdSet, area, vec2,
|
Vec2, ViewportId, ViewportIdMap, ViewportIdSet,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod theme;
|
mod theme;
|
||||||
@@ -115,7 +115,7 @@ pub struct Memory {
|
|||||||
/// If position is [`None`], the popup position will be calculated based on some configuration
|
/// If position is [`None`], the popup position will be calculated based on some configuration
|
||||||
/// (e.g. relative to some other widget).
|
/// (e.g. relative to some other widget).
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
popups: ViewportIdMap<OpenPopup>,
|
popups: ViewportIdMap<HashMap<LayerId, OpenPopup>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Memory {
|
impl Default for Memory {
|
||||||
@@ -743,7 +743,7 @@ impl Memory {
|
|||||||
// Cleanup
|
// Cleanup
|
||||||
self.interactions.retain(|id, _| viewports.contains(id));
|
self.interactions.retain(|id, _| viewports.contains(id));
|
||||||
self.areas.retain(|id, _| viewports.contains(id));
|
self.areas.retain(|id, _| viewports.contains(id));
|
||||||
self.popups.retain(|id, _| viewports.contains(id));
|
// self.popups.retain(|id, _| viewports.contains(id)); TODO
|
||||||
|
|
||||||
self.areas.entry(self.viewport_id).or_default();
|
self.areas.entry(self.viewport_id).or_default();
|
||||||
|
|
||||||
@@ -763,12 +763,12 @@ impl Memory {
|
|||||||
self.focus_mut().end_pass(used_ids);
|
self.focus_mut().end_pass(used_ids);
|
||||||
|
|
||||||
// Clean up abandoned popups.
|
// Clean up abandoned popups.
|
||||||
if let Some(popup) = self.popups.get_mut(&self.viewport_id) {
|
if let Some(popups) = self.popups.get_mut(&self.viewport_id) {
|
||||||
if popup.open_this_frame {
|
popups.retain(|_, popup| {
|
||||||
|
let open_this_frame = popup.open_this_frame;
|
||||||
popup.open_this_frame = false;
|
popup.open_this_frame = false;
|
||||||
} else {
|
open_this_frame
|
||||||
self.popups.remove(&self.viewport_id);
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1013,23 +1013,31 @@ impl Memory {
|
|||||||
pub fn is_popup_open(&self, popup_id: Id) -> bool {
|
pub fn is_popup_open(&self, popup_id: Id) -> bool {
|
||||||
self.popups
|
self.popups
|
||||||
.get(&self.viewport_id)
|
.get(&self.viewport_id)
|
||||||
.is_some_and(|state| state.id == popup_id)
|
.is_some_and(|state| {
|
||||||
|
state.values().any(|popup| popup.id == popup_id)
|
||||||
|
})
|
||||||
|| self.everything_is_visible()
|
|| self.everything_is_visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is any popup open?
|
/// Is any popup open?
|
||||||
#[deprecated = "Use Popup::is_any_open instead"]
|
#[deprecated = "Use Popup::is_any_open instead"]
|
||||||
pub fn any_popup_open(&self) -> bool {
|
pub fn any_popup_open(&self) -> bool {
|
||||||
self.popups.contains_key(&self.viewport_id) || self.everything_is_visible()
|
self.popups
|
||||||
|
.get(&self.viewport_id)
|
||||||
|
.is_some_and(|state| !state.is_empty())
|
||||||
|
|| self.everything_is_visible()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the given popup and close all others.
|
/// Open the given popup and close all others.
|
||||||
///
|
///
|
||||||
/// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open.
|
/// Note that you must call `keep_popup_open` on subsequent frames as long as the popup is open.
|
||||||
#[deprecated = "Use Popup::open_id instead"]
|
#[deprecated = "Use Popup::open_id instead"]
|
||||||
pub fn open_popup(&mut self, popup_id: Id) {
|
pub fn open_popup(&mut self, layer: LayerId, popup_id: Id) {
|
||||||
|
// TODO: Close non-parent popups
|
||||||
self.popups
|
self.popups
|
||||||
.insert(self.viewport_id, OpenPopup::new(popup_id, None));
|
.entry(self.viewport_id)
|
||||||
|
.or_default()
|
||||||
|
.insert(layer, OpenPopup::new(popup_id, None));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Popups must call this every frame while open.
|
/// Popups must call this every frame while open.
|
||||||
@@ -1038,26 +1046,34 @@ impl Memory {
|
|||||||
/// called. For example, when a context menu is open and the underlying widget stops
|
/// called. For example, when a context menu is open and the underlying widget stops
|
||||||
/// being rendered.
|
/// being rendered.
|
||||||
#[deprecated = "Use Popup::show instead"]
|
#[deprecated = "Use Popup::show instead"]
|
||||||
pub fn keep_popup_open(&mut self, popup_id: Id) {
|
pub fn keep_popup_open(&mut self, layer_id: LayerId, popup_id: Id) {
|
||||||
if let Some(state) = self.popups.get_mut(&self.viewport_id)
|
if let Some(state) = self.popups.get_mut(&self.viewport_id) {
|
||||||
&& state.id == popup_id
|
if let Some(popup) = state.get_mut(&layer_id) {
|
||||||
{
|
if popup.id == popup_id {
|
||||||
state.open_this_frame = true;
|
popup.open_this_frame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the popup and remember its position.
|
/// Open the popup and remember its position.
|
||||||
#[deprecated = "Use Popup with PopupAnchor::Position instead"]
|
#[deprecated = "Use Popup with PopupAnchor::Position instead"]
|
||||||
pub fn open_popup_at(&mut self, popup_id: Id, pos: impl Into<Option<Pos2>>) {
|
pub fn open_popup_at(&mut self, layer_id: LayerId, popup_id: Id, pos: impl Into<Option<Pos2>>) {
|
||||||
self.popups
|
self.popups
|
||||||
.insert(self.viewport_id, OpenPopup::new(popup_id, pos.into()));
|
.entry(self.viewport_id)
|
||||||
|
.or_default()
|
||||||
|
.insert(layer_id, OpenPopup::new(popup_id, pos.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the position for this popup.
|
/// Get the position for this popup.
|
||||||
#[deprecated = "Use Popup::position_of_id instead"]
|
#[deprecated = "Use Popup::position_of_id instead"]
|
||||||
pub fn popup_position(&self, id: Id) -> Option<Pos2> {
|
pub fn popup_position(&self, id: Id) -> Option<Pos2> {
|
||||||
let state = self.popups.get(&self.viewport_id)?;
|
self.popups.get(&self.viewport_id).and_then(|state| {
|
||||||
if state.id == id { state.pos } else { None }
|
state
|
||||||
|
.values()
|
||||||
|
.find(|popup| popup.id == id)
|
||||||
|
.and_then(|popup| popup.pos)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close any currently open popup.
|
/// Close any currently open popup.
|
||||||
@@ -1071,9 +1087,8 @@ impl Memory {
|
|||||||
/// See also [`Self::close_all_popups`] if you want to close any / all currently open popups.
|
/// See also [`Self::close_all_popups`] if you want to close any / all currently open popups.
|
||||||
#[deprecated = "Use Popup::close_id instead"]
|
#[deprecated = "Use Popup::close_id instead"]
|
||||||
pub fn close_popup(&mut self, popup_id: Id) {
|
pub fn close_popup(&mut self, popup_id: Id) {
|
||||||
#[expect(deprecated)]
|
if let Some(state) = self.popups.get_mut(&self.viewport_id) {
|
||||||
if self.is_popup_open(popup_id) {
|
state.retain(|_, popup| popup.id != popup_id);
|
||||||
self.popups.remove(&self.viewport_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1081,12 +1096,11 @@ impl Memory {
|
|||||||
///
|
///
|
||||||
/// Note: At most, only one popup can be open at a time.
|
/// Note: At most, only one popup can be open at a time.
|
||||||
#[deprecated = "Use Popup::toggle_id instead"]
|
#[deprecated = "Use Popup::toggle_id instead"]
|
||||||
pub fn toggle_popup(&mut self, popup_id: Id) {
|
pub fn toggle_popup(&mut self, layer_id: LayerId, popup_id: Id) {
|
||||||
#[expect(deprecated)]
|
|
||||||
if self.is_popup_open(popup_id) {
|
if self.is_popup_open(popup_id) {
|
||||||
self.close_popup(popup_id);
|
self.close_popup(popup_id);
|
||||||
} else {
|
} else {
|
||||||
self.open_popup(popup_id);
|
self.open_popup(layer_id, popup_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1294,6 +1308,17 @@ impl Areas {
|
|||||||
self.sublayers.get(&layer_id).into_iter().flatten().copied()
|
self.sublayers.get(&layer_id).into_iter().flatten().copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_child_recursive(&self, parent: LayerId, child: LayerId) -> bool {
|
||||||
|
if let Some(children) = self.sublayers.get(&parent) {
|
||||||
|
for &c in children {
|
||||||
|
if c == child || self.is_child_recursive(c, child) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
|
pub(crate) fn is_sublayer(&self, layer: &LayerId) -> bool {
|
||||||
self.parent_layer(*layer).is_some()
|
self.parent_layer(*layer).is_some()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense, Tooltip, Ui,
|
emath::{Align, Pos2, Rect, Vec2}, pass_state, Context, CursorIcon, Id, LayerId, PointerButton, Popup, PopupKind, Sense,
|
||||||
WidgetRect, WidgetText,
|
Tooltip, Ui,
|
||||||
emath::{Align, Pos2, Rect, Vec2},
|
WidgetRect,
|
||||||
pass_state,
|
WidgetText,
|
||||||
};
|
};
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -259,6 +259,28 @@ impl Response {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clicked_elsewhere_excluding_child_layers(&self) -> bool {
|
||||||
|
let clicked_elsewhere = self.clicked_elsewhere();
|
||||||
|
if !clicked_elsewhere {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pos) = self.ctx.input(|i| i.pointer.interact_pos()) {
|
||||||
|
let layer_under_pointer = self.ctx.layer_id_at(pos);
|
||||||
|
if let Some(layer_under_pointer) = layer_under_pointer {
|
||||||
|
let child_clicked = self.ctx.memory(|mem| {
|
||||||
|
mem.areas()
|
||||||
|
.is_child_recursive(self.layer_id, layer_under_pointer)
|
||||||
|
});
|
||||||
|
!child_clicked
|
||||||
|
} else {
|
||||||
|
clicked_elsewhere
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clicked_elsewhere
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Was the widget enabled?
|
/// Was the widget enabled?
|
||||||
/// If false, there was no interaction attempted
|
/// If false, there was no interaction attempted
|
||||||
/// and the widget should be drawn in a gray disabled look.
|
/// and the widget should be drawn in a gray disabled look.
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::rust_view_ui;
|
use crate::rust_view_ui;
|
||||||
use egui::color_picker::{Alpha, color_picker_color32};
|
use egui::color_picker::{color_picker_color32, Alpha};
|
||||||
use egui::containers::menu::{MenuConfig, SubMenuButton};
|
use egui::containers::menu::{MenuConfig, SubMenuButton};
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, Align2, Atom, Button, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior, RectAlign,
|
include_image, Align, Align2, Atom, Button, ComboBox, Frame, Id, Layout, Popup, PopupCloseBehavior,
|
||||||
RichText, Tooltip, Ui, UiBuilder, include_image,
|
RectAlign, RichText, Tooltip, Ui, UiBuilder,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Showcase [`Popup`].
|
/// Showcase [`Popup`].
|
||||||
@@ -119,6 +119,13 @@ impl PopupsDemo {
|
|||||||
if ui.button("Open…").clicked() {
|
if ui.button("Open…").clicked() {
|
||||||
ui.close();
|
ui.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ComboBox::new("Combobox in menu", "")
|
||||||
|
.selected_text(if self.checked { "Option 1" } else { "Option 2" })
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.selectable_value(&mut self.checked, true, "Option 1");
|
||||||
|
ui.selectable_value(&mut self.checked, false, "Option 2");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ pub struct Node<'tree> {
|
|||||||
pub(crate) queue: &'tree EventQueue,
|
pub(crate) queue: &'tree EventQueue,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Node<'_> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.accesskit_node.id() == other.accesskit_node.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Node<'_> {}
|
||||||
|
|
||||||
impl Debug for Node<'_> {
|
impl Debug for Node<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
debug_fmt_node(self, f)
|
debug_fmt_node(self, f)
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
use kittest::Queryable as _;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_interactive_tooltip() {
|
|
||||||
struct State {
|
|
||||||
link_clicked: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut harness = egui_kittest::Harness::new_ui_state(
|
|
||||||
|ui, state| {
|
|
||||||
ui.label("I have a tooltip").on_hover_ui(|ui| {
|
|
||||||
if ui.link("link").clicked() {
|
|
||||||
state.link_clicked = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
link_clicked: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
harness.get_by_label_contains("tooltip").hover();
|
|
||||||
harness.run();
|
|
||||||
harness.get_by_label("link").hover();
|
|
||||||
harness.run();
|
|
||||||
harness.get_by_label("link").click();
|
|
||||||
|
|
||||||
harness.run();
|
|
||||||
|
|
||||||
assert!(harness.state().link_clicked);
|
|
||||||
}
|
|
||||||
3
tests/egui_tests/tests/snapshots/combobox_in_popup.png
Normal file
3
tests/egui_tests/tests/snapshots/combobox_in_popup.png
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:e8876d1b19c1d8c0cac30edc022913ca88ba7afaedf677e9c5de07c3b45e3b8a
|
||||||
|
size 10595
|
||||||
88
tests/egui_tests/tests/test_popups.rs
Normal file
88
tests/egui_tests/tests/test_popups.rs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
use egui::{ComboBox, Popup, PopupCloseBehavior, Vec2};
|
||||||
|
use egui_kittest::kittest::Queryable as _;
|
||||||
|
use egui_kittest::Harness;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_interactive_tooltip() {
|
||||||
|
struct State {
|
||||||
|
link_clicked: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut harness = egui_kittest::Harness::new_ui_state(
|
||||||
|
|ui, state| {
|
||||||
|
ui.label("I have a tooltip").on_hover_ui(|ui| {
|
||||||
|
if ui.link("link").clicked() {
|
||||||
|
state.link_clicked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
link_clicked: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.get_by_label_contains("tooltip").hover();
|
||||||
|
harness.run();
|
||||||
|
harness.get_by_label("link").hover();
|
||||||
|
harness.run();
|
||||||
|
harness.get_by_label("link").click();
|
||||||
|
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
assert!(harness.state().link_clicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_combobox_in_popup() {
|
||||||
|
let mut harness = Harness::builder()
|
||||||
|
.with_size(Vec2::new(200.0, 200.0))
|
||||||
|
.build_ui_state(
|
||||||
|
|ui, state| {
|
||||||
|
let response = ui.button("Open Popup");
|
||||||
|
Popup::menu(&response)
|
||||||
|
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
|
||||||
|
.show(|ui| {
|
||||||
|
ui.heading("Popup");
|
||||||
|
ComboBox::new("combo", "")
|
||||||
|
.selected_text("Select an option")
|
||||||
|
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
ui.selectable_value(state, 0, "Option 0");
|
||||||
|
ui.selectable_value(state, 1, "Option 1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
harness.get_by_label("Open Popup").click();
|
||||||
|
harness.run();
|
||||||
|
harness.get_by_value("Select an option").click();
|
||||||
|
harness.run();
|
||||||
|
harness.get_by_label("Option 1").click();
|
||||||
|
harness.run();
|
||||||
|
assert_eq!(*harness.state(), 1);
|
||||||
|
|
||||||
|
// The parent popup should not close when clicking on the child popup
|
||||||
|
harness.get_by_label("Option 0").click();
|
||||||
|
harness.run();
|
||||||
|
assert_eq!(*harness.state(), 0);
|
||||||
|
|
||||||
|
harness.snapshot("combobox_in_popup");
|
||||||
|
|
||||||
|
// Clicking the parent popup should close the child popup
|
||||||
|
harness.get_by_label("Popup").click();
|
||||||
|
harness.run();
|
||||||
|
assert_eq!(harness.query_by_label("Option 0"), None);
|
||||||
|
|
||||||
|
harness.get_by_value("Select an option").click();
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
assert_eq!(harness.query_by_label("Option 0").is_some(), true);
|
||||||
|
|
||||||
|
// Clicking outside should close both popups
|
||||||
|
harness.get_by_label("Open Popup").click();
|
||||||
|
harness.run();
|
||||||
|
|
||||||
|
assert_eq!(harness.query_by_label("Popup"), None);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user