egui/egui/src/context.rs

961 lines
35 KiB
Rust

// #![warn(missing_docs)]
use std::sync::{
atomic::{AtomicU32, Ordering::SeqCst},
Arc,
};
use crate::{
animation_manager::AnimationManager,
data::output::Output,
frame_state::FrameState,
input_state::*,
layers::GraphicLayers,
mutex::{Mutex, MutexGuard},
*,
};
use epaint::{stats::*, text::Fonts, *};
// ----------------------------------------------------------------------------
/// A wrapper around [`Arc`](std::sync::Arc)`<`[`Context`]`>`.
/// This is how you will normally create and access a [`Context`].
///
/// Almost all methods are marked `&self`, `Context` has interior mutability (protected by mutexes).
///
/// [`CtxRef`] is cheap to clone, and any clones refers to the same mutable data.
///
/// # Example:
///
/// ``` no_run
/// # fn handle_output(_: egui::Output) {}
/// # fn paint(_: Vec<egui::ClippedMesh>) {}
/// let mut ctx = egui::CtxRef::default();
///
/// // Game loop:
/// loop {
/// let raw_input = egui::RawInput::default();
/// ctx.begin_frame(raw_input);
///
/// egui::CentralPanel::default().show(&ctx, |ui| {
/// ui.label("Hello world!");
/// if ui.button("Click me").clicked() {
/// /* take some action here */
/// }
/// });
///
/// let (output, shapes) = ctx.end_frame();
/// let clipped_meshes = ctx.tessellate(shapes); // create triangles to paint
/// handle_output(output);
/// paint(clipped_meshes);
/// }
/// ```
///
#[derive(Clone)]
pub struct CtxRef(std::sync::Arc<Context>);
impl std::ops::Deref for CtxRef {
type Target = Context;
fn deref(&self) -> &Context {
&*self.0
}
}
impl AsRef<Context> for CtxRef {
fn as_ref(&self) -> &Context {
self.0.as_ref()
}
}
impl std::borrow::Borrow<Context> for CtxRef {
fn borrow(&self) -> &Context {
self.0.borrow()
}
}
impl std::cmp::PartialEq for CtxRef {
fn eq(&self, other: &CtxRef) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Default for CtxRef {
fn default() -> Self {
Self(Arc::new(Context {
// Start with painting an extra frame to compensate for some widgets
// that take two frames before they "settle":
repaint_requests: AtomicU32::new(1),
..Context::default()
}))
}
}
impl CtxRef {
/// Call at the start of every frame. Match with a call to [`Context::end_frame`].
///
/// This will modify the internal reference to point to a new generation of [`Context`].
/// Any old clones of this [`CtxRef`] will refer to the old [`Context`], which will not get new input.
///
/// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
pub fn begin_frame(&mut self, new_input: RawInput) {
let mut self_: Context = (*self.0).clone();
self_.begin_frame_mut(new_input);
*self = Self(Arc::new(self_));
}
// ---------------------------------------------------------------------
/// If the given [`Id`] is not unique, an error will be printed at the given position.
/// Call this for [`Id`]:s that need interaction or persistence.
pub(crate) fn register_interaction_id(&self, id: Id, new_rect: Rect) {
let prev_rect = self.frame_state().used_ids.insert(id, new_rect);
if let Some(prev_rect) = prev_rect {
// it is ok to reuse the same ID for e.g. a frame around a widget,
// or to check for interaction with the same widget twice:
if prev_rect.expand(0.1).contains_rect(new_rect)
|| new_rect.expand(0.1).contains_rect(prev_rect)
{
return;
}
let show_error = |pos: Pos2, text: String| {
let painter = self.debug_painter();
let rect = painter.error(pos, text);
if let Some(pointer_pos) = self.input.pointer.hover_pos() {
if rect.contains(pointer_pos) {
painter.error(
rect.left_bottom() + vec2(2.0, 4.0),
"ID clashes happens when things like Windows or CollapsingHeaders share names,\n\
or when things like ScrollAreas and Resize areas aren't given unique id_source:s.",
);
}
}
};
let id_str = id.short_debug_format();
if prev_rect.min.distance(new_rect.min) < 4.0 {
show_error(new_rect.min, format!("Double use of ID {}", id_str));
} else {
show_error(prev_rect.min, format!("First use of ID {}", id_str));
show_error(new_rect.min, format!("Second use of ID {}", id_str));
}
}
}
// ---------------------------------------------------------------------
/// Use `ui.interact` instead
#[allow(clippy::too_many_arguments)]
pub(crate) fn interact(
&self,
clip_rect: Rect,
item_spacing: Vec2,
layer_id: LayerId,
id: Id,
rect: Rect,
sense: Sense,
enabled: bool,
) -> Response {
let gap = 0.5; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient).
// Make it easier to click things:
let interact_rect = rect.expand2(
(0.5 * item_spacing - Vec2::splat(gap))
.at_least(Vec2::splat(0.0))
.at_most(Vec2::splat(5.0)),
); // make it easier to click
let hovered = self.rect_contains_pointer(layer_id, clip_rect.intersect(interact_rect));
self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered)
}
/// You specify if a thing is hovered, and the function gives a `Response`.
pub(crate) fn interact_with_hovered(
&self,
layer_id: LayerId,
id: Id,
rect: Rect,
sense: Sense,
enabled: bool,
hovered: bool,
) -> Response {
let hovered = hovered && enabled; // can't even hover disabled widgets
let mut response = Response {
ctx: self.clone(),
layer_id,
id,
rect,
sense,
enabled,
hovered,
clicked: Default::default(),
double_clicked: Default::default(),
dragged: false,
drag_released: false,
is_pointer_button_down_on: false,
interact_pointer_pos: None,
changed: false, // must be set by the widget itself
};
if !enabled || !sense.focusable || !layer_id.allow_interaction() {
// Not interested or allowed input:
self.memory().surrender_focus(id);
return response;
}
// We only want to focus labels if the screen reader is on.
let interested_in_focus =
sense.interactive() || sense.focusable && self.memory().options.screen_reader;
if interested_in_focus {
self.memory().interested_in_focus(id);
}
if sense.click
&& response.has_focus()
&& (self.input().key_pressed(Key::Space) || self.input().key_pressed(Key::Enter))
{
// Space/enter works like a primary click for e.g. selected buttons
response.clicked[PointerButton::Primary as usize] = true;
}
self.register_interaction_id(id, rect);
if sense.click || sense.drag {
let mut memory = self.memory();
memory.interaction.click_interest |= hovered && sense.click;
memory.interaction.drag_interest |= hovered && sense.drag;
response.dragged = memory.interaction.drag_id == Some(id);
response.is_pointer_button_down_on =
memory.interaction.click_id == Some(id) || response.dragged;
for pointer_event in &self.input.pointer.pointer_events {
match pointer_event {
PointerEvent::Moved(_) => {}
PointerEvent::Pressed(_) => {
if hovered {
if sense.click && memory.interaction.click_id.is_none() {
// potential start of a click
memory.interaction.click_id = Some(id);
response.is_pointer_button_down_on = true;
}
// HACK: windows have low priority on dragging.
// This is so that if you drag a slider in a window,
// the slider will steal the drag away from the window.
// This is needed because we do window interaction first (to prevent frame delay),
// and then do content layout.
if sense.drag
&& (memory.interaction.drag_id.is_none()
|| memory.interaction.drag_is_window)
{
// potential start of a drag
memory.interaction.drag_id = Some(id);
memory.interaction.drag_is_window = false;
memory.window_interaction = None; // HACK: stop moving windows (if any)
response.is_pointer_button_down_on = true;
response.dragged = true;
}
}
}
PointerEvent::Released(click) => {
response.drag_released = response.dragged;
response.dragged = false;
if hovered && response.is_pointer_button_down_on {
if let Some(click) = click {
let clicked = hovered && response.is_pointer_button_down_on;
response.clicked[click.button as usize] = clicked;
response.double_clicked[click.button as usize] =
clicked && click.is_double();
}
}
}
}
}
}
if response.is_pointer_button_down_on {
response.interact_pointer_pos = self.input().pointer.interact_pos();
}
if self.input.pointer.any_down() {
response.hovered &= response.is_pointer_button_down_on; // we don't hover widgets while interacting with *other* widgets
}
if response.has_focus() && response.clicked_elsewhere() {
self.memory().surrender_focus(id);
}
response
}
/// Get a full-screen painter for a new or existing layer
pub fn layer_painter(&self, layer_id: LayerId) -> Painter {
Painter::new(self.clone(), layer_id, self.input.screen_rect())
}
/// Paint on top of everything else
pub fn debug_painter(&self) -> Painter {
Self::layer_painter(self, LayerId::debug())
}
}
// ----------------------------------------------------------------------------
/// This is the first thing you need when working with egui. Create using [`CtxRef`].
///
/// Contains the [`InputState`], [`Memory`], [`Output`], and more.
///
/// Your handle to Egui.
///
/// Almost all methods are marked `&self`, `Context` has interior mutability (protected by mutexes).
/// Multi-threaded access to a [`Context`] is behind the feature flag `multi_threaded`.
/// Normally you'd always do all ui work on one thread, or perhaps use multiple contexts,
/// but if you really want to access the same Context from multiple threads, it *SHOULD* be fine,
/// but you are likely the first person to try it.
#[derive(Default)]
pub struct Context {
// We clone the Context each frame so we can set a new `input`.
// This is so we can avoid a mutex lock to access the `InputState`.
// This means everything else needs to be behind an Arc.
// We can probably come up with a nicer design.
//
/// None until first call to `begin_frame`.
fonts: Option<Arc<Fonts>>,
memory: Arc<Mutex<Memory>>,
animation_manager: Arc<Mutex<AnimationManager>>,
input: InputState,
/// State that is collected during a frame and then cleared
frame_state: Arc<Mutex<FrameState>>,
// The output of a frame:
graphics: Arc<Mutex<GraphicLayers>>,
output: Arc<Mutex<Output>>,
paint_stats: Arc<Mutex<PaintStats>>,
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: AtomicU32,
}
impl Clone for Context {
fn clone(&self) -> Self {
Context {
fonts: self.fonts.clone(),
memory: self.memory.clone(),
animation_manager: self.animation_manager.clone(),
input: self.input.clone(),
frame_state: self.frame_state.clone(),
graphics: self.graphics.clone(),
output: self.output.clone(),
paint_stats: self.paint_stats.clone(),
repaint_requests: self.repaint_requests.load(SeqCst).into(),
}
}
}
impl Context {
/// How much space is still available after panels has been added.
/// This is the "background" area, what egui doesn't cover with panels (but may cover with windows).
/// This is also the area to which windows are constrained.
pub fn available_rect(&self) -> Rect {
self.frame_state.lock().available_rect()
}
/// Stores all the egui state.
/// If you want to store/restore egui, serialize this.
pub fn memory(&self) -> MutexGuard<'_, Memory> {
self.memory.lock()
}
pub(crate) fn graphics(&self) -> MutexGuard<'_, GraphicLayers> {
self.graphics.lock()
}
/// What egui outputs each frame.
pub fn output(&self) -> MutexGuard<'_, Output> {
self.output.lock()
}
pub(crate) fn frame_state(&self) -> MutexGuard<'_, FrameState> {
self.frame_state.lock()
}
/// Call this if there is need to repaint the UI, i.e. if you are showing an animation.
/// If this is called at least once in a frame, then there will be another frame right after this.
/// Call as many times as you wish, only one repaint will be issued.
pub fn request_repaint(&self) {
// request two frames of repaint, just to cover some corner cases (frame delays):
let times_to_repaint = 2;
self.repaint_requests.store(times_to_repaint, SeqCst);
}
#[inline(always)]
pub fn input(&self) -> &InputState {
&self.input
}
/// Not valid until first call to [`CtxRef::begin_frame()`].
/// That's because since we don't know the proper `pixels_per_point` until then.
pub fn fonts(&self) -> &Fonts {
&*self
.fonts
.as_ref()
.expect("No fonts available until first call to CtxRef::begin_frame()")
}
/// The egui texture, containing font characters etc.
/// Not valid until first call to [`CtxRef::begin_frame()`].
/// That's because since we don't know the proper `pixels_per_point` until then.
pub fn texture(&self) -> Arc<epaint::Texture> {
self.fonts().texture()
}
/// Tell `egui` which fonts to use.
///
/// The default `egui` fonts only support latin and cyrillic alphabets,
/// but you can call this to install additional fonts that support e.g. korean characters.
///
/// The new fonts will become active at the start of the next frame.
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
if let Some(current_fonts) = &self.fonts {
// NOTE: this comparison is expensive since it checks TTF data for equality
if current_fonts.definitions() == &font_definitions {
return; // no change - save us from reloading font textures
}
}
self.memory().new_font_definitions = Some(font_definitions);
}
/// The [`Style`] used by all subsequent windows, panels etc.
pub fn style(&self) -> Arc<Style> {
self.memory().options.style.clone()
}
/// The [`Style`] used by all new windows, panels etc.
///
/// You can also use [`Ui::style_mut`] to change the style of a single [`Ui`].
///
/// Example:
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// let mut style: egui::Style = (*ctx.style()).clone();
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// ctx.set_style(style);
/// ```
pub fn set_style(&self, style: impl Into<Arc<Style>>) {
self.memory().options.style = style.into();
}
/// The [`Visuals`] used by all subsequent windows, panels etc.
///
/// You can also use [`Ui::visuals_mut`] to change the visuals of a single [`Ui`].
///
/// Example:
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// ctx.set_visuals(egui::Visuals::light()); // Switch to light mode
/// ```
pub fn set_visuals(&self, visuals: crate::Visuals) {
std::sync::Arc::make_mut(&mut self.memory().options.style).visuals = visuals;
}
/// The number of physical pixels for each logical point.
#[inline(always)]
pub fn pixels_per_point(&self) -> f32 {
self.input.pixels_per_point()
}
/// Set the number of physical pixels for each logical point.
/// Will become active at the start of the next frame.
///
/// Note that this may be overwritten by input from the integration via [`RawInput::pixels_per_point`].
/// For instance, when using `egui_web` the browsers native zoom level will always be used.
pub fn set_pixels_per_point(&self, pixels_per_point: f32) {
self.memory().new_pixels_per_point = Some(pixels_per_point);
}
/// Useful for pixel-perfect rendering
pub(crate) fn round_to_pixel(&self, point: f32) -> f32 {
let pixels_per_point = self.pixels_per_point();
(point * pixels_per_point).round() / pixels_per_point
}
/// Useful for pixel-perfect rendering
pub(crate) fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
}
/// Useful for pixel-perfect rendering
pub(crate) fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
}
/// Useful for pixel-perfect rendering
pub(crate) fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
Rect {
min: self.round_pos_to_pixels(rect.min),
max: self.round_pos_to_pixels(rect.max),
}
}
// ---------------------------------------------------------------------
/// Constrain the position of a window/area so it fits within the provided boundary.
///
/// If area is `None`, will constrain to [`Self::available_rect`].
pub(crate) fn constrain_window_rect_to_area(&self, window: Rect, area: Option<Rect>) -> Rect {
let mut area = area.unwrap_or_else(|| self.available_rect());
if window.width() > area.width() {
// Allow overlapping side bars.
// This is important for small screens, e.g. mobiles running the web demo.
area.max.x = self.input().screen_rect().max.x;
area.min.x = self.input().screen_rect().min.x;
}
if window.height() > area.height() {
// Allow overlapping top/bottom bars:
area.max.y = self.input().screen_rect().max.y;
area.min.y = self.input().screen_rect().min.y;
}
let mut pos = window.min;
// Constrain to screen, unless window is too large to fit:
let margin_x = (window.width() - area.width()).at_least(0.0);
let margin_y = (window.height() - area.height()).at_least(0.0);
pos.x = pos.x.at_most(area.right() + margin_x - window.width()); // move left if needed
pos.x = pos.x.at_least(area.left() - margin_x); // move right if needed
pos.y = pos.y.at_most(area.bottom() + margin_y - window.height()); // move right if needed
pos.y = pos.y.at_least(area.top() - margin_y); // move down if needed
pos = self.round_pos_to_pixels(pos);
Rect::from_min_size(pos, window.size())
}
// ---------------------------------------------------------------------
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
self.memory().begin_frame(&self.input, &new_raw_input);
let mut input = std::mem::take(&mut self.input);
if let Some(new_pixels_per_point) = self.memory().new_pixels_per_point.take() {
input.pixels_per_point = new_pixels_per_point;
}
self.input = input.begin_frame(new_raw_input);
self.frame_state.lock().begin_frame(&self.input);
{
// Load new fonts if required:
let new_font_definitions = self.memory().new_font_definitions.take();
let pixels_per_point = self.input.pixels_per_point();
let pixels_per_point_changed = match &self.fonts {
None => true,
Some(current_fonts) => {
(current_fonts.pixels_per_point() - pixels_per_point).abs() > 1e-3
}
};
if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed {
self.fonts = Some(Arc::new(Fonts::new(
pixels_per_point,
new_font_definitions.unwrap_or_else(|| {
self.fonts
.as_ref()
.map(|font| font.definitions().clone())
.unwrap_or_default()
}),
)));
}
}
// Ensure we register the background area so panels and background ui can catch clicks:
let screen_rect = self.input.screen_rect();
self.memory().areas.set_state(
LayerId::background(),
containers::area::State {
pos: screen_rect.min,
size: screen_rect.size(),
interactable: true,
},
);
}
/// Call at the end of each frame.
/// Returns what has happened this frame [`crate::Output`] as well as what you need to paint.
/// You can transform the returned shapes into triangles with a call to [`Context::tessellate`].
#[must_use]
pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
if self.input.wants_repaint() {
self.request_repaint();
}
self.memory()
.end_frame(&self.input, &self.frame_state().used_ids);
self.fonts().end_frame();
let mut output: Output = std::mem::take(&mut self.output());
if self.repaint_requests.load(SeqCst) > 0 {
self.repaint_requests.fetch_sub(1, SeqCst);
output.needs_repaint = true;
}
let shapes = self.drain_paint_lists();
(output, shapes)
}
fn drain_paint_lists(&self) -> Vec<ClippedShape> {
let memory = self.memory();
self.graphics().drain(memory.areas.order()).collect()
}
/// Tessellate the given shapes into triangle meshes.
pub fn tessellate(&self, shapes: Vec<ClippedShape>) -> Vec<ClippedMesh> {
let mut tessellation_options = self.memory().options.tessellation_options;
tessellation_options.pixels_per_point = self.pixels_per_point();
tessellation_options.aa_size = 1.0 / self.pixels_per_point();
let paint_stats = PaintStats::from_shapes(&shapes); // TODO: internal allocations
let clipped_meshes = tessellator::tessellate_shapes(
shapes,
tessellation_options,
self.fonts().texture().size(),
);
*self.paint_stats.lock() = paint_stats.with_clipped_meshes(&clipped_meshes);
clipped_meshes
}
// ---------------------------------------------------------------------
/// How much space is used by panels and windows.
pub fn used_rect(&self) -> Rect {
let mut used = self.frame_state().used_by_panels;
for window in self.memory().areas.visible_windows() {
used = used.union(window.rect());
}
used
}
/// How much space is used by panels and windows.
/// You can shrink your egui area to this size and still fit all egui components.
pub fn used_size(&self) -> Vec2 {
self.used_rect().max - Pos2::new(0.0, 0.0)
}
// ---------------------------------------------------------------------
/// Is the pointer (mouse/touch) over any egui area?
pub fn is_pointer_over_area(&self) -> bool {
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
if let Some(layer) = self.layer_id_at(pointer_pos) {
if layer.order == Order::Background {
!self.frame_state().unused_rect.contains(pointer_pos)
} else {
true
}
} else {
false
}
} else {
false
}
}
/// True if egui is currently interested in the pointer (mouse or touch).
/// Could be the pointer is hovering over a [`Window`] or the user is dragging a widget.
/// If `false`, the pointer is outside of any egui area and so
/// you may be interested in what it is doing (e.g. controlling your game).
/// Returns `false` if a drag started outside of egui and then moved over an egui area.
pub fn wants_pointer_input(&self) -> bool {
self.is_using_pointer() || (self.is_pointer_over_area() && !self.input().pointer.any_down())
}
/// Is egui currently using the pointer position (e.g. dragging a slider).
/// NOTE: this will return `false` if the pointer is just hovering over an egui area.
pub fn is_using_pointer(&self) -> bool {
self.memory().interaction.is_using_pointer()
}
/// If `true`, egui is currently listening on text input (e.g. typing text in a [`TextEdit`]).
pub fn wants_keyboard_input(&self) -> bool {
self.memory().interaction.focus.focused().is_some()
}
// ---------------------------------------------------------------------
/// Move all the graphics at the given layer.
/// Can be used to implement drag-and-drop (see relevant demo).
pub fn translate_layer(&self, layer_id: LayerId, delta: Vec2) {
if delta != Vec2::ZERO {
self.graphics().list(layer_id).lock().translate(delta);
}
}
/// Top-most layer at the given position.
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
let resize_grab_radius_side = self.style().interaction.resize_grab_radius_side;
self.memory().layer_id_at(pos, resize_grab_radius_side)
}
pub(crate) fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
rect.contains(pointer_pos) && self.layer_id_at(pointer_pos) == Some(layer_id)
} else {
false
}
}
// ---------------------------------------------------------------------
/// Wether or not to debug widget layout on hover.
pub fn debug_on_hover(&self) -> bool {
self.memory().options.style.debug.debug_on_hover
}
/// Turn on/off wether or not to debug widget layout on hover.
pub fn set_debug_on_hover(&self, debug_on_hover: bool) {
let mut style = (*self.memory().options.style).clone();
style.debug.debug_on_hover = debug_on_hover;
self.set_style(style);
}
}
/// ## Animation
impl Context {
/// Returns a value in the range [0, 1], to indicate "how on" this thing is.
///
/// The first time called it will return `if value { 1.0 } else { 0.0 }`
/// Calling this with `value = true` will always yield a number larger than zero, quickly going towards one.
/// Calling this with `value = false` will always yield a number less than one, quickly going towards zero.
///
/// The function will call [`Self::request_repaint()`] when appropriate.
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
let animation_time = self.style().animation_time;
let animated_value =
self.animation_manager
.lock()
.animate_bool(&self.input, animation_time, id, value);
let animation_in_progress = 0.0 < animated_value && animated_value < 1.0;
if animation_in_progress {
self.request_repaint();
}
animated_value
}
/// Clear memory of any animations.
pub fn clear_animations(&self) {
*self.animation_manager.lock() = Default::default();
}
}
impl Context {
pub fn settings_ui(&self, ui: &mut Ui) {
use crate::containers::*;
CollapsingHeader::new("🎑 Style")
.default_open(true)
.show(ui, |ui| {
self.style_ui(ui);
});
CollapsingHeader::new("🔠 Fonts")
.default_open(false)
.show(ui, |ui| {
let mut font_definitions = self.fonts().definitions().clone();
font_definitions.ui(ui);
self.fonts().texture().ui(ui);
self.set_fonts(font_definitions);
});
CollapsingHeader::new("✒ Painting")
.default_open(true)
.show(ui, |ui| {
let mut tessellation_options = self.memory().options.tessellation_options;
tessellation_options.ui(ui);
ui.vertical_centered(|ui| reset_button(ui, &mut tessellation_options));
self.memory().options.tessellation_options = tessellation_options;
});
}
pub fn inspection_ui(&self, ui: &mut Ui) {
use crate::containers::*;
crate::trace!(ui);
ui.label(format!("Is using pointer: {}", self.is_using_pointer()))
.on_hover_text(
"Is egui currently using the pointer actively (e.g. dragging a slider)?",
);
ui.label(format!("Wants pointer input: {}", self.wants_pointer_input()))
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
ui.label(format!(
"Wants keyboard input: {}",
self.wants_keyboard_input()
))
.on_hover_text("Is egui currently listening for text input?");
ui.label(format!(
"Keyboard focus widget: {}",
self.memory()
.interaction
.focus
.focused()
.as_ref()
.map(Id::short_debug_format)
.unwrap_or_default()
))
.on_hover_text("Is egui currently listening for text input?");
let pointer_pos = self
.input()
.pointer
.hover_pos()
.map_or_else(String::new, |pos| format!("{:?}", pos));
ui.label(format!("Pointer pos: {}", pointer_pos));
let top_layer = self
.input()
.pointer
.hover_pos()
.and_then(|pos| self.layer_id_at(pos))
.map_or_else(String::new, |layer| layer.short_debug_format());
ui.label(format!("Top layer under mouse: {}", top_layer));
ui.add_space(16.0);
ui.label(format!(
"There are {} text galleys in the layout cache",
self.fonts().num_galleys_in_cache()
))
.on_hover_text("This is approximately the number of text strings on screen");
ui.add_space(16.0);
CollapsingHeader::new("📥 Input")
.default_open(false)
.show(ui, |ui| ui.input().clone().ui(ui));
CollapsingHeader::new("📊 Paint stats")
.default_open(true)
.show(ui, |ui| {
self.paint_stats.lock().ui(ui);
});
}
pub fn memory_ui(&self, ui: &mut crate::Ui) {
if ui
.button("Reset all")
.on_hover_text("Reset all egui state")
.clicked()
{
*self.memory() = Default::default();
}
ui.horizontal(|ui| {
ui.label(format!(
"{} areas (panels, windows, popups, …)",
self.memory().areas.count()
));
if ui.button("Reset").clicked() {
self.memory().areas = Default::default();
}
});
ui.indent("areas", |ui| {
ui.label("Visible areas, ordered back to front.");
ui.label("Hover to highlight");
let layers_ids: Vec<LayerId> = self.memory().areas.order().to_vec();
for layer_id in layers_ids {
let area = self.memory().areas.get(layer_id.id).cloned();
if let Some(area) = area {
let is_visible = self.memory().areas.is_visible(&layer_id);
if !is_visible {
continue;
}
let text = format!("{} - {:?}", layer_id.short_debug_format(), area.rect(),);
// TODO: `Sense::hover_highlight()`
if ui
.add(Label::new(text).monospace().sense(Sense::click()))
.hovered
&& is_visible
{
ui.ctx()
.debug_painter()
.debug_rect(area.rect(), Color32::RED, "");
}
}
}
});
ui.horizontal(|ui| {
ui.label(format!(
"{} collapsing headers",
self.memory()
.id_data
.count::<containers::collapsing_header::State>()
));
if ui.button("Reset").clicked() {
self.memory()
.id_data
.remove_by_type::<containers::collapsing_header::State>();
}
});
ui.horizontal(|ui| {
ui.label(format!(
"{} menu bars",
self.memory().id_data_temp.count::<menu::BarState>()
));
if ui.button("Reset").clicked() {
self.memory()
.id_data_temp
.remove_by_type::<menu::BarState>();
}
});
ui.horizontal(|ui| {
ui.label(format!(
"{} scroll areas",
self.memory().id_data.count::<scroll_area::State>()
));
if ui.button("Reset").clicked() {
self.memory().id_data.remove_by_type::<scroll_area::State>();
}
});
ui.horizontal(|ui| {
ui.label(format!(
"{} resize areas",
self.memory().id_data.count::<resize::State>()
));
if ui.button("Reset").clicked() {
self.memory().id_data.remove_by_type::<resize::State>();
}
});
ui.shrink_width_to_current(); // don't let the text below grow this window wider
ui.label("NOTE: the position of this window cannot be reset from within itself.");
ui.collapsing("Interaction", |ui| {
let interaction = self.memory().interaction.clone();
interaction.ui(ui);
});
}
}
impl Context {
pub fn style_ui(&self, ui: &mut Ui) {
let mut style: Style = (*self.style()).clone();
style.ui(ui);
self.set_style(style);
}
}