From f7b6e310abf1b84c81af74e7d666d5b5e5ee5b4c Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Fri, 26 Apr 2024 19:56:41 +0900 Subject: [PATCH 01/18] Update response.rs --- crates/egui/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 44eb4f74bd6..6ab818bd87f 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -356,7 +356,7 @@ impl Response { /// The widget was being dragged by the button, but now it has been released. pub fn drag_stopped_by(&self, button: PointerButton) -> bool { - self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button)) + self.dragged_by(button) && !self.is_pointer_button_down_on() } /// The widget was being dragged, but now it has been released. From 3cb9577e6cce3d7e6854732e0677e1f3786bb876 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sat, 27 Apr 2024 17:24:56 +0900 Subject: [PATCH 02/18] Update response.rs --- crates/egui/src/response.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/egui/src/response.rs b/crates/egui/src/response.rs index 6ab818bd87f..79e4ec83f5a 100644 --- a/crates/egui/src/response.rs +++ b/crates/egui/src/response.rs @@ -356,7 +356,8 @@ impl Response { /// The widget was being dragged by the button, but now it has been released. pub fn drag_stopped_by(&self, button: PointerButton) -> bool { - self.dragged_by(button) && !self.is_pointer_button_down_on() + (self.drag_stopped() && self.ctx.input(|i| i.pointer.button_released(button))) + || (self.dragged_by(button) && !self.is_pointer_button_down_on()) } /// The widget was being dragged, but now it has been released. From 23ffc877d89fc167e2b02b28dcf48b5727c0fbab Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:10:37 +0900 Subject: [PATCH 03/18] Update context.rs --- crates/egui/src/context.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 192b0cc49d4..e2ed6c29c94 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1877,6 +1877,7 @@ impl Context { drag_stopped: _, contains_pointer, hovered, + pointer_button: _, } = interact_widgets; if true { From c06e9343e523f5e4cb92291aedd94b12c81407e3 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:21:29 +0900 Subject: [PATCH 04/18] Update interaction.rs --- crates/egui/src/interaction.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 8a7b2d0948c..6bee7f57de3 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -54,6 +54,10 @@ pub struct InteractionSnapshot { /// This is usually a larger set than [`Self::hovered`], /// and can be used for e.g. drag-and-drop zones. pub contains_pointer: IdSet, + + /// State of pointer button + /// Be careful when using it because the state is not perfect. + pub pointer_button: [bool; NUM_POINTER_BUTTONS], } impl InteractionSnapshot { @@ -66,6 +70,7 @@ impl InteractionSnapshot { drag_stopped, hovered, contains_pointer, + pointer_button, } = self; fn id_ui<'a>(ui: &mut crate::Ui, widgets: impl IntoIterator) { @@ -102,6 +107,11 @@ impl InteractionSnapshot { ui.label("contains_pointer"); id_ui(ui, contains_pointer); ui.end_row(); + + ui.label("pointer_button"); + let text = format!("{:?}", pointer_button); + ui.label(text); + ui.end_row(); }); } } @@ -133,6 +143,7 @@ pub(crate) fn interact( let mut clicked = None; let mut dragged = prev_snapshot.dragged; let mut long_touched = None; + let mut pointer_button = prev_snapshot.pointer_button; if input.is_long_touch() { // We implement "press-and-hold for context menu" on touch screens here @@ -153,7 +164,11 @@ pub(crate) fn interact( match pointer_event { PointerEvent::Moved(_) => {} - PointerEvent::Pressed { .. } => { + PointerEvent::Pressed { + position: _, + button, + } => { + pointer_button[*button as usize] = true; // Maybe new click? if interaction.potential_click_id.is_none() { interaction.potential_click_id = hits.click.map(|w| w.id); @@ -165,7 +180,8 @@ pub(crate) fn interact( } } - PointerEvent::Released { click, button: _ } => { + PointerEvent::Released { click, button } => { + pointer_button[*button as usize] = false; if click.is_some() && !input.pointer.is_decidedly_dragging() { if let Some(widget) = interaction .potential_click_id @@ -285,5 +301,6 @@ pub(crate) fn interact( drag_stopped, contains_pointer, hovered, + pointer_button, } } From a1b91471a1eda47484781426366bae839dd57c11 Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:24:23 +0900 Subject: [PATCH 05/18] Update context.rs --- crates/egui/src/context.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index e2ed6c29c94..aaa7e5cdf7e 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -1870,14 +1870,14 @@ impl Context { { let interact_widgets = self.write(|ctx| ctx.viewport().interact_widgets.clone()); let InteractionSnapshot { + pointer_button: _, clicked, long_touched: _, drag_started: _, dragged, drag_stopped: _, - contains_pointer, hovered, - pointer_button: _, + contains_pointer, } = interact_widgets; if true { From 99470e56308b3a44ac4bbc6b569a35c94241ec3f Mon Sep 17 00:00:00 2001 From: rustbasic <127506429+rustbasic@users.noreply.github.com> Date: Sun, 28 Apr 2024 10:29:17 +0900 Subject: [PATCH 06/18] Update interaction.rs --- crates/egui/src/interaction.rs | 3593 +++++++++++++++++++++++++++++--- 1 file changed, 3351 insertions(+), 242 deletions(-) diff --git a/crates/egui/src/interaction.rs b/crates/egui/src/interaction.rs index 6bee7f57de3..38fbbcecc85 100644 --- a/crates/egui/src/interaction.rs +++ b/crates/egui/src/interaction.rs @@ -1,306 +1,3415 @@ -//! How mouse and touch interzcts with widgets. +#![warn(missing_docs)] // Let's keep `Context` well-documented. -use crate::*; +use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration}; -use self::{hit_test::WidgetHits, id::IdSet, input_state::PointerEvent, memory::InteractionState}; +use epaint::{ + emath::TSTransform, mutex::*, stats::*, text::Fonts, util::OrderedFloat, TessellationOptions, *, +}; -/// Calculated at the start of each frame -/// based on: -/// * Widget rects from precious frame -/// * Mouse/touch input -/// * Current [`InteractionState`]. +use crate::{ + animation_manager::AnimationManager, + data::output::PlatformOutput, + frame_state::FrameState, + input_state::*, + layers::GraphicLayers, + load::{Bytes, Loaders, SizedTexture}, + memory::Options, + os::OperatingSystem, + output::FullOutput, + util::IdTypeMap, + viewport::ViewportClass, + TextureHandle, ViewportCommand, *, +}; + +use self::{hit_test::WidgetHits, interaction::InteractionSnapshot}; + +/// Information given to the backend about when it is time to repaint the ui. +/// +/// This is given in the callback set by [`Context::set_request_repaint_callback`]. +#[derive(Clone, Copy, Debug)] +pub struct RequestRepaintInfo { + /// This is used to specify what viewport that should repaint. + pub viewport_id: ViewportId, + + /// Repaint after this duration. If zero, repaint as soon as possible. + pub delay: Duration, + + /// The current frame number. + /// + /// This can be compared to [`Context::frame_nr`] to see if we've already + /// triggered the painting of the next frame. + pub current_frame_nr: u64, +} + +// ---------------------------------------------------------------------------- + +thread_local! { + static IMMEDIATE_VIEWPORT_RENDERER: RefCell>> = Default::default(); +} + +// ---------------------------------------------------------------------------- + +struct WrappedTextureManager(Arc>); + +impl Default for WrappedTextureManager { + fn default() -> Self { + let mut tex_mngr = epaint::textures::TextureManager::default(); + + // Will be filled in later + let font_id = tex_mngr.alloc( + "egui_font_texture".into(), + epaint::FontImage::new([0, 0]).into(), + Default::default(), + ); + assert_eq!(font_id, TextureId::default()); + + Self(Arc::new(RwLock::new(tex_mngr))) + } +} + +// ---------------------------------------------------------------------------- + +/// Generic event callback. +pub type ContextCallback = Arc; + +#[derive(Clone)] +struct NamedContextCallback { + debug_name: &'static str, + callback: ContextCallback, +} + +/// Callbacks that users can register #[derive(Clone, Default)] -pub struct InteractionSnapshot { - /// The widget that got clicked this frame. - pub clicked: Option, +struct Plugins { + pub on_begin_frame: Vec, + pub on_end_frame: Vec, +} + +impl Plugins { + fn call(ctx: &Context, _cb_name: &str, callbacks: &[NamedContextCallback]) { + crate::profile_scope!("plugins", _cb_name); + for NamedContextCallback { + debug_name: _name, + callback, + } in callbacks + { + crate::profile_scope!("plugin", _name); + (callback)(ctx); + } + } + + fn on_begin_frame(&self, ctx: &Context) { + Self::call(ctx, "on_begin_frame", &self.on_begin_frame); + } + + fn on_end_frame(&self, ctx: &Context) { + Self::call(ctx, "on_end_frame", &self.on_end_frame); + } +} + +// ---------------------------------------------------------------------------- + +/// Repaint-logic +impl ContextImpl { + /// This is where we update the repaint logic. + fn begin_frame_repaint_logic(&mut self, viewport_id: ViewportId) { + let viewport = self.viewports.entry(viewport_id).or_default(); + + std::mem::swap( + &mut viewport.repaint.prev_causes, + &mut viewport.repaint.causes, + ); + viewport.repaint.causes.clear(); + + viewport.repaint.prev_frame_paint_delay = viewport.repaint.repaint_delay; + + if viewport.repaint.outstanding == 0 { + // We are repainting now, so we can wait a while for the next repaint. + viewport.repaint.repaint_delay = Duration::MAX; + } else { + viewport.repaint.repaint_delay = Duration::ZERO; + viewport.repaint.outstanding -= 1; + if let Some(callback) = &self.request_repaint_callback { + (callback)(RequestRepaintInfo { + viewport_id, + delay: Duration::ZERO, + current_frame_nr: viewport.repaint.frame_nr, + }); + } + } + } + + fn request_repaint(&mut self, viewport_id: ViewportId, cause: RepaintCause) { + self.request_repaint_after(Duration::ZERO, viewport_id, cause); + } + + fn request_repaint_after( + &mut self, + delay: Duration, + viewport_id: ViewportId, + cause: RepaintCause, + ) { + let viewport = self.viewports.entry(viewport_id).or_default(); + + if delay == Duration::ZERO { + // Each request results in two repaints, just to give some things time to settle. + // This solves some corner-cases of missing repaints on frame-delayed responses. + viewport.repaint.outstanding = 1; + } else { + // For non-zero delays, we only repaint once, because + // otherwise we would just schedule an immediate repaint _now_, + // which would then clear the delay and repaint again. + // Hovering a tooltip is a good example of a case where we want to repaint after a delay. + } + + viewport.repaint.causes.push(cause); + + // We save some CPU time by only calling the callback if we need to. + // If the new delay is greater or equal to the previous lowest, + // it means we have already called the callback, and don't need to do it again. + if delay < viewport.repaint.repaint_delay { + viewport.repaint.repaint_delay = delay; + + if let Some(callback) = &self.request_repaint_callback { + (callback)(RequestRepaintInfo { + viewport_id, + delay, + current_frame_nr: viewport.repaint.frame_nr, + }); + } + } + } + + #[must_use] + fn requested_immediate_repaint_prev_frame(&self, viewport_id: &ViewportId) -> bool { + self.viewports.get(viewport_id).map_or(false, |v| { + v.repaint.requested_immediate_repaint_prev_frame() + }) + } + + #[must_use] + fn has_requested_repaint(&self, viewport_id: &ViewportId) -> bool { + self.viewports.get(viewport_id).map_or(false, |v| { + 0 < v.repaint.outstanding || v.repaint.repaint_delay < Duration::MAX + }) + } +} + +// ---------------------------------------------------------------------------- + +/// State stored per viewport +#[derive(Default)] +struct ViewportState { + /// The type of viewport. + /// + /// This will never be [`ViewportClass::Embedded`], + /// since those don't result in real viewports. + class: ViewportClass, + + /// The latest delta + builder: ViewportBuilder, + + /// The user-code that shows the GUI, used for deferred viewports. + /// + /// `None` for immediate viewports. + viewport_ui_cb: Option>, + + input: InputState, + + /// State that is collected during a frame and then cleared + frame_state: FrameState, + + /// Has this viewport been updated this frame? + used: bool, + + /// Written to during the frame. + widgets_this_frame: WidgetRects, + + /// Read + widgets_prev_frame: WidgetRects, + + /// State related to repaint scheduling. + repaint: ViewportRepaintInfo, + + // ---------------------- + // Updated at the start of the frame: + // + /// Which widgets are under the pointer? + hits: WidgetHits, + + /// What widgets are being interacted with this frame? + /// + /// Based on the widgets from last frame, and input in this frame. + interact_widgets: InteractionSnapshot, + + // ---------------------- + // The output of a frame: + // + graphics: GraphicLayers, + // Most of the things in `PlatformOutput` are not actually viewport dependent. + output: PlatformOutput, + commands: Vec, +} + +/// What called [`Context::request_repaint`]? +#[derive(Clone)] +pub struct RepaintCause { + /// What file had the call that requested the repaint? + pub file: &'static str, + + /// What line number of the the call that requested the repaint? + pub line: u32, +} + +impl std::fmt::Debug for RepaintCause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.file, self.line) + } +} + +impl RepaintCause { + /// Capture the file and line number of the call site. + #[allow(clippy::new_without_default)] + #[track_caller] + pub fn new() -> Self { + let caller = Location::caller(); + Self { + file: caller.file(), + line: caller.line(), + } + } +} + +impl std::fmt::Display for RepaintCause { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.file, self.line) + } +} + +/// Per-viewport state related to repaint scheduling. +struct ViewportRepaintInfo { + /// Monotonically increasing counter. + frame_nr: u64, + + /// The duration which the backend will poll for new events + /// before forcing another egui update, even if there's no new events. + /// + /// Also used to suppress multiple calls to the repaint callback during the same frame. + /// + /// This is also returned in [`crate::ViewportOutput`]. + repaint_delay: Duration, + + /// While positive, keep requesting repaints. Decrement at the start of each frame. + outstanding: u8, + + /// What caused repaints during this frame? + causes: Vec, + + /// What triggered a repaint the previous frame? + /// (i.e: why are we updating now?) + prev_causes: Vec, + + /// What was the output of `repaint_delay` on the previous frame? + /// + /// If this was zero, we are repainting as quickly as possible + /// (as far as we know). + prev_frame_paint_delay: Duration, +} + +impl Default for ViewportRepaintInfo { + fn default() -> Self { + Self { + frame_nr: 0, + + // We haven't scheduled a repaint yet. + repaint_delay: Duration::MAX, + + // Let's run a couple of frames at the start, because why not. + outstanding: 1, + + causes: Default::default(), + prev_causes: Default::default(), + + prev_frame_paint_delay: Duration::MAX, + } + } +} + +impl ViewportRepaintInfo { + pub fn requested_immediate_repaint_prev_frame(&self) -> bool { + self.prev_frame_paint_delay == Duration::ZERO + } +} + +// ---------------------------------------------------------------------------- + +#[derive(Default)] +struct ContextImpl { + /// Since we could have multiple viewports across multiple monitors with + /// different `pixels_per_point`, we need a `Fonts` instance for each unique + /// `pixels_per_point`. + /// This is because the `Fonts` depend on `pixels_per_point` for the font atlas + /// as well as kerning, font sizes, etc. + fonts: std::collections::BTreeMap, Fonts>, + font_definitions: FontDefinitions, + + memory: Memory, + animation_manager: AnimationManager, + + plugins: Plugins, + + /// All viewports share the same texture manager and texture namespace. + /// + /// In all viewports, [`TextureId::default`] is special, and points to the font atlas. + /// The font-atlas texture _may_ be different across viewports, as they may have different + /// `pixels_per_point`, so we do special book-keeping for that. + /// See . + tex_manager: WrappedTextureManager, + + /// Set during the frame, becomes active at the start of the next frame. + new_zoom_factor: Option, + + os: OperatingSystem, + + /// How deeply nested are we? + viewport_stack: Vec, + + /// What is the last viewport rendered? + last_viewport: ViewportId, + + paint_stats: PaintStats, + + request_repaint_callback: Option>, + + viewport_parents: ViewportIdMap, + viewports: ViewportIdMap, + + embed_viewports: bool, + + #[cfg(feature = "accesskit")] + is_accesskit_enabled: bool, + #[cfg(feature = "accesskit")] + accesskit_node_classes: accesskit::NodeClassSet, + + loaders: Arc, +} + +impl ContextImpl { + fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) { + let viewport_id = new_raw_input.viewport_id; + let parent_id = new_raw_input + .viewports + .get(&viewport_id) + .and_then(|v| v.parent) + .unwrap_or_default(); + let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent_id); + + let is_outermost_viewport = self.viewport_stack.is_empty(); // not necessarily root, just outermost immediate viewport + self.viewport_stack.push(ids); + + self.begin_frame_repaint_logic(viewport_id); + + let viewport = self.viewports.entry(viewport_id).or_default(); + + if is_outermost_viewport { + if let Some(new_zoom_factor) = self.new_zoom_factor.take() { + let ratio = self.memory.options.zoom_factor / new_zoom_factor; + self.memory.options.zoom_factor = new_zoom_factor; + + let input = &viewport.input; + // This is a bit hacky, but is required to avoid jitter: + let mut rect = input.screen_rect; + rect.min = (ratio * rect.min.to_vec2()).to_pos2(); + rect.max = (ratio * rect.max.to_vec2()).to_pos2(); + new_raw_input.screen_rect = Some(rect); + // We should really scale everything else in the input too, + // but the `screen_rect` is the most important part. + } + } + let native_pixels_per_point = new_raw_input + .viewport() + .native_pixels_per_point + .unwrap_or(1.0); + let pixels_per_point = self.memory.options.zoom_factor * native_pixels_per_point; + + let all_viewport_ids: ViewportIdSet = self.all_viewport_ids(); + + let viewport = self.viewports.entry(self.viewport_id()).or_default(); + + self.memory.begin_frame(&new_raw_input, &all_viewport_ids); + + viewport.input = std::mem::take(&mut viewport.input).begin_frame( + new_raw_input, + viewport.repaint.requested_immediate_repaint_prev_frame(), + pixels_per_point, + ); + + let screen_rect = viewport.input.screen_rect; + + viewport.frame_state.begin_frame(screen_rect); + + { + let area_order = self.memory.areas().order_map(); + + let mut layers: Vec = viewport.widgets_prev_frame.layer_ids().collect(); + + layers.sort_by(|a, b| { + if a.order == b.order { + // Maybe both are windows, so respect area order: + area_order.get(a).cmp(&area_order.get(b)) + } else { + // comparing e.g. background to tooltips + a.order.cmp(&b.order) + } + }); + + viewport.hits = if let Some(pos) = viewport.input.pointer.interact_pos() { + let interact_radius = self.memory.options.style.interaction.interact_radius; + + crate::hit_test::hit_test( + &viewport.widgets_prev_frame, + &layers, + &self.memory.layer_transforms, + pos, + interact_radius, + ) + } else { + WidgetHits::default() + }; + + viewport.interact_widgets = crate::interaction::interact( + &viewport.interact_widgets, + &viewport.widgets_prev_frame, + &viewport.hits, + &viewport.input, + self.memory.interaction_mut(), + ); + } + + // Ensure we register the background area so panels and background ui can catch clicks: + self.memory.areas_mut().set_state( + LayerId::background(), + containers::area::State { + pivot_pos: screen_rect.left_top(), + pivot: Align2::LEFT_TOP, + size: screen_rect.size(), + interactable: true, + }, + ); + + #[cfg(feature = "accesskit")] + if self.is_accesskit_enabled { + crate::profile_scope!("accesskit"); + use crate::frame_state::AccessKitFrameState; + let id = crate::accesskit_root_id(); + let mut builder = accesskit::NodeBuilder::new(accesskit::Role::Window); + let pixels_per_point = viewport.input.pixels_per_point(); + builder.set_transform(accesskit::Affine::scale(pixels_per_point.into())); + let mut node_builders = IdMap::default(); + node_builders.insert(id, builder); + viewport.frame_state.accesskit_state = Some(AccessKitFrameState { + node_builders, + parent_stack: vec![id], + }); + } + + self.update_fonts_mut(); + } + + /// Load fonts unless already loaded. + fn update_fonts_mut(&mut self) { + crate::profile_function!(); + + let input = &self.viewport().input; + let pixels_per_point = input.pixels_per_point(); + let max_texture_side = input.max_texture_side; + + if let Some(font_definitions) = self.memory.new_font_definitions.take() { + // New font definition loaded, so we need to reload all fonts. + self.fonts.clear(); + self.font_definitions = font_definitions; + #[cfg(feature = "log")] + log::debug!("Loading new font definitions"); + } + + let mut is_new = false; + + let fonts = self + .fonts + .entry(pixels_per_point.into()) + .or_insert_with(|| { + #[cfg(feature = "log")] + log::trace!("Creating new Fonts for pixels_per_point={pixels_per_point}"); + + is_new = true; + crate::profile_scope!("Fonts::new"); + Fonts::new( + pixels_per_point, + max_texture_side, + self.font_definitions.clone(), + ) + }); + + { + crate::profile_scope!("Fonts::begin_frame"); + fonts.begin_frame(pixels_per_point, max_texture_side); + } + + if is_new && self.memory.options.preload_font_glyphs { + crate::profile_scope!("preload_font_glyphs"); + // Preload the most common characters for the most common fonts. + // This is not very important to do, but may save a few GPU operations. + for font_id in self.memory.options.style.text_styles.values() { + fonts.lock().fonts.font(font_id).preload_common_characters(); + } + } + } + + #[cfg(feature = "accesskit")] + fn accesskit_node_builder(&mut self, id: Id) -> &mut accesskit::NodeBuilder { + let state = self + .viewport() + .frame_state + .accesskit_state + .as_mut() + .unwrap(); + let builders = &mut state.node_builders; + if let std::collections::hash_map::Entry::Vacant(entry) = builders.entry(id) { + entry.insert(Default::default()); + let parent_id = state.parent_stack.last().unwrap(); + let parent_builder = builders.get_mut(parent_id).unwrap(); + parent_builder.push_child(id.accesskit_id()); + } + builders.get_mut(&id).unwrap() + } + + fn pixels_per_point(&mut self) -> f32 { + self.viewport().input.pixels_per_point + } + + /// Return the `ViewportId` of the current viewport. + /// + /// For the root viewport this will return [`ViewportId::ROOT`]. + pub(crate) fn viewport_id(&self) -> ViewportId { + self.viewport_stack.last().copied().unwrap_or_default().this + } + + /// Return the `ViewportId` of his parent. + /// + /// For the root viewport this will return [`ViewportId::ROOT`]. + pub(crate) fn parent_viewport_id(&self) -> ViewportId { + let viewport_id = self.viewport_id(); + *self + .viewport_parents + .get(&viewport_id) + .unwrap_or(&ViewportId::ROOT) + } + + /// Return the `ViewportId` of his parent. + pub(crate) fn parent_viewport_id_of(&self, viewport_id: ViewportId) -> ViewportId { + *self + .viewport_parents + .get(&viewport_id) + .unwrap_or(&ViewportId::ROOT) + } + + fn all_viewport_ids(&self) -> ViewportIdSet { + self.viewports + .keys() + .copied() + .chain([ViewportId::ROOT]) + .collect() + } + + /// The current active viewport + pub(crate) fn viewport(&mut self) -> &mut ViewportState { + self.viewports.entry(self.viewport_id()).or_default() + } + + fn viewport_for(&mut self, viewport_id: ViewportId) -> &mut ViewportState { + self.viewports.entry(viewport_id).or_default() + } +} + +// ---------------------------------------------------------------------------- + +/// Your handle to egui. +/// +/// This is the first thing you need when working with egui. +/// Contains the [`InputState`], [`Memory`], [`PlatformOutput`], and more. +/// +/// [`Context`] is cheap to clone, and any clones refers to the same mutable data +/// ([`Context`] uses refcounting internally). +/// +/// ## Locking +/// All methods are marked `&self`; [`Context`] has interior mutability protected by an [`RwLock`]. +/// +/// To access parts of a `Context` you need to use some of the helper functions that take closures: +/// +/// ``` +/// # let ctx = egui::Context::default(); +/// if ctx.input(|i| i.key_pressed(egui::Key::A)) { +/// ctx.output_mut(|o| o.copied_text = "Hello!".to_string()); +/// } +/// ``` +/// +/// Within such a closure you may NOT recursively lock the same [`Context`], as that can lead to a deadlock. +/// Therefore it is important that any lock of [`Context`] is short-lived. +/// +/// These are effectively transactional accesses. +/// +/// [`Ui`] has many of the same accessor functions, and the same applies there. +/// +/// ## Example: +/// +/// ``` no_run +/// # fn handle_platform_output(_: egui::PlatformOutput) {} +/// # fn paint(textures_delta: egui::TexturesDelta, _: Vec) {} +/// let mut ctx = egui::Context::default(); +/// +/// // Game loop: +/// loop { +/// let raw_input = egui::RawInput::default(); +/// let full_output = ctx.run(raw_input, |ctx| { +/// egui::CentralPanel::default().show(&ctx, |ui| { +/// ui.label("Hello world!"); +/// if ui.button("Click me").clicked() { +/// // take some action here +/// } +/// }); +/// }); +/// handle_platform_output(full_output.platform_output); +/// let clipped_primitives = ctx.tessellate(full_output.shapes, full_output.pixels_per_point); +/// paint(full_output.textures_delta, clipped_primitives); +/// } +/// ``` +#[derive(Clone)] +pub struct Context(Arc>); + +impl std::fmt::Debug for Context { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Context").finish_non_exhaustive() + } +} + +impl std::cmp::PartialEq for Context { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Default for Context { + fn default() -> Self { + let ctx_impl = ContextImpl { + embed_viewports: true, + ..Default::default() + }; + let ctx = Self(Arc::new(RwLock::new(ctx_impl))); + + // Register built-in plugins: + crate::debug_text::register(&ctx); + crate::text_selection::LabelSelectionState::register(&ctx); + crate::DragAndDrop::register(&ctx); + + ctx + } +} + +impl Context { + /// Do read-only (shared access) transaction on Context + fn read(&self, reader: impl FnOnce(&ContextImpl) -> R) -> R { + reader(&self.0.read()) + } + + /// Do read-write (exclusive access) transaction on Context + fn write(&self, writer: impl FnOnce(&mut ContextImpl) -> R) -> R { + writer(&mut self.0.write()) + } + + /// Run the ui code for one frame. + /// + /// Put your widgets into a [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`]. + /// + /// This will modify the internal reference to point to a new generation of [`Context`]. + /// Any old clones of this [`Context`] will refer to the old [`Context`], which will not get new input. + /// + /// You can alternatively run [`Self::begin_frame`] and [`Context::end_frame`]. + /// + /// ``` + /// // One egui context that you keep reusing: + /// let mut ctx = egui::Context::default(); + /// + /// // Each frame: + /// let input = egui::RawInput::default(); + /// let full_output = ctx.run(input, |ctx| { + /// egui::CentralPanel::default().show(&ctx, |ui| { + /// ui.label("Hello egui!"); + /// }); + /// }); + /// // handle full_output + /// ``` + #[must_use] + pub fn run(&self, new_input: RawInput, run_ui: impl FnOnce(&Self)) -> FullOutput { + crate::profile_function!(); + + self.begin_frame(new_input); + run_ui(self); + self.end_frame() + } + + /// An alternative to calling [`Self::run`]. + /// + /// ``` + /// // One egui context that you keep reusing: + /// let mut ctx = egui::Context::default(); + /// + /// // Each frame: + /// let input = egui::RawInput::default(); + /// ctx.begin_frame(input); + /// + /// egui::CentralPanel::default().show(&ctx, |ui| { + /// ui.label("Hello egui!"); + /// }); + /// + /// let full_output = ctx.end_frame(); + /// // handle full_output + /// ``` + pub fn begin_frame(&self, new_input: RawInput) { + crate::profile_function!(); + self.read(|ctx| ctx.plugins.clone()).on_begin_frame(self); + self.write(|ctx| ctx.begin_frame_mut(new_input)); + } +} + +/// ## Borrows parts of [`Context`] +/// These functions all lock the [`Context`]. +/// Please see the documentation of [`Context`] for how locking works! +impl Context { + /// Read-only access to [`InputState`]. + /// + /// Note that this locks the [`Context`]. + /// + /// ``` + /// # let mut ctx = egui::Context::default(); + /// ctx.input(|i| { + /// // ⚠️ Using `ctx` (even from other `Arc` reference) again here will lead to a deadlock! + /// }); + /// + /// if let Some(pos) = ctx.input(|i| i.pointer.hover_pos()) { + /// // This is fine! + /// } + /// ``` + #[inline] + pub fn input(&self, reader: impl FnOnce(&InputState) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().input)) + } + + /// This will create a `InputState::default()` if there is no input state for that viewport + #[inline] + pub fn input_for(&self, id: ViewportId, reader: impl FnOnce(&InputState) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport_for(id).input)) + } + + /// Read-write access to [`InputState`]. + #[inline] + pub fn input_mut(&self, writer: impl FnOnce(&mut InputState) -> R) -> R { + self.input_mut_for(self.viewport_id(), writer) + } + + /// This will create a `InputState::default()` if there is no input state for that viewport + #[inline] + pub fn input_mut_for(&self, id: ViewportId, writer: impl FnOnce(&mut InputState) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.viewport_for(id).input)) + } + + /// Read-only access to [`Memory`]. + #[inline] + pub fn memory(&self, reader: impl FnOnce(&Memory) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory)) + } + + /// Read-write access to [`Memory`]. + #[inline] + pub fn memory_mut(&self, writer: impl FnOnce(&mut Memory) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.memory)) + } + + /// Read-only access to [`IdTypeMap`], which stores superficial widget state. + #[inline] + pub fn data(&self, reader: impl FnOnce(&IdTypeMap) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory.data)) + } + + /// Read-write access to [`IdTypeMap`], which stores superficial widget state. + #[inline] + pub fn data_mut(&self, writer: impl FnOnce(&mut IdTypeMap) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.memory.data)) + } + + /// Read-write access to [`GraphicLayers`], where painted [`crate::Shape`]s are written to. + #[inline] + pub fn graphics_mut(&self, writer: impl FnOnce(&mut GraphicLayers) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.viewport().graphics)) + } + + /// Read-only access to [`GraphicLayers`], where painted [`crate::Shape`]s are written to. + #[inline] + pub fn graphics(&self, reader: impl FnOnce(&GraphicLayers) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().graphics)) + } + + /// Read-only access to [`PlatformOutput`]. + /// + /// This is what egui outputs each frame. + /// + /// ``` + /// # let mut ctx = egui::Context::default(); + /// ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::Progress); + /// ``` + #[inline] + pub fn output(&self, reader: impl FnOnce(&PlatformOutput) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().output)) + } + + /// Read-write access to [`PlatformOutput`]. + #[inline] + pub fn output_mut(&self, writer: impl FnOnce(&mut PlatformOutput) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.viewport().output)) + } + + /// Read-only access to [`FrameState`]. + #[inline] + pub(crate) fn frame_state(&self, reader: impl FnOnce(&FrameState) -> R) -> R { + self.write(move |ctx| reader(&ctx.viewport().frame_state)) + } + + /// Read-write access to [`FrameState`]. + #[inline] + pub(crate) fn frame_state_mut(&self, writer: impl FnOnce(&mut FrameState) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.viewport().frame_state)) + } + + /// Read-only access to [`Fonts`]. + /// + /// Not valid until first call to [`Context::run()`]. + /// That's because since we don't know the proper `pixels_per_point` until then. + #[inline] + pub fn fonts(&self, reader: impl FnOnce(&Fonts) -> R) -> R { + self.write(move |ctx| { + let pixels_per_point = ctx.pixels_per_point(); + reader( + ctx.fonts + .get(&pixels_per_point.into()) + .expect("No fonts available until first call to Context::run()"), + ) + }) + } + + /// Read-only access to [`Options`]. + #[inline] + pub fn options(&self, reader: impl FnOnce(&Options) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory.options)) + } + + /// Read-write access to [`Options`]. + #[inline] + pub fn options_mut(&self, writer: impl FnOnce(&mut Options) -> R) -> R { + self.write(move |ctx| writer(&mut ctx.memory.options)) + } + + /// Read-only access to [`TessellationOptions`]. + #[inline] + pub fn tessellation_options(&self, reader: impl FnOnce(&TessellationOptions) -> R) -> R { + self.read(move |ctx| reader(&ctx.memory.options.tessellation_options)) + } + + /// Read-write access to [`TessellationOptions`]. + #[inline] + pub fn tessellation_options_mut( + &self, + writer: impl FnOnce(&mut TessellationOptions) -> R, + ) -> R { + self.write(move |ctx| writer(&mut ctx.memory.options.tessellation_options)) + } + + /// If the given [`Id`] has been used previously the same frame at at different position, + /// then an error will be printed on screen. + /// + /// This function is already called for all widgets that do any interaction, + /// but you can call this from widgets that store state but that does not interact. + /// + /// The given [`Rect`] should be approximately where the widget will be. + /// The most important thing is that [`Rect::min`] is approximately correct, + /// because that's where the warning will be painted. If you don't know what size to pick, just pick [`Vec2::ZERO`]. + pub fn check_for_id_clash(&self, id: Id, new_rect: Rect, what: &str) { + let prev_rect = self.frame_state_mut(move |state| state.used_ids.insert(id, new_rect)); + + if !self.options(|opt| opt.warn_on_id_clash) { + return; + } + + let Some(prev_rect) = prev_rect else { return }; + + // 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: + let is_same_rect = prev_rect.expand(0.1).contains_rect(new_rect) + || new_rect.expand(0.1).contains_rect(prev_rect); + if is_same_rect { + return; + } + + let show_error = |widget_rect: Rect, text: String| { + let screen_rect = self.screen_rect(); + + let text = format!("🔥 {text}"); + let color = self.style().visuals.error_fg_color; + let painter = self.debug_painter(); + painter.rect_stroke(widget_rect, 0.0, (1.0, color)); + + let below = widget_rect.bottom() + 32.0 < screen_rect.bottom(); + + let text_rect = if below { + painter.debug_text( + widget_rect.left_bottom() + vec2(0.0, 2.0), + Align2::LEFT_TOP, + color, + text, + ) + } else { + painter.debug_text( + widget_rect.left_top() - vec2(0.0, 2.0), + Align2::LEFT_BOTTOM, + color, + text, + ) + }; + + if let Some(pointer_pos) = self.pointer_hover_pos() { + if text_rect.contains(pointer_pos) { + let tooltip_pos = if below { + text_rect.left_bottom() + vec2(2.0, 4.0) + } else { + text_rect.left_top() + vec2(2.0, -4.0) + }; + + painter.error( + tooltip_pos, + format!("Widget is {} this text.\n\n\ + ID clashes happens when things like Windows or CollapsingHeaders share names,\n\ + or when things like Plot and Grid:s aren't given unique id_source:s.\n\n\ + Sometimes the solution is to use ui.push_id.", + if below { "above" } else { "below" }) + ); + } + } + }; + + let id_str = id.short_debug_format(); + + if prev_rect.min.distance(new_rect.min) < 4.0 { + show_error(new_rect, format!("Double use of {what} ID {id_str}")); + } else { + show_error(prev_rect, format!("First use of {what} ID {id_str}")); + show_error(new_rect, format!("Second use of {what} ID {id_str}")); + } + } + + // --------------------------------------------------------------------- + + /// Create a widget and check for interaction. + /// + /// If this is not called, the widget doesn't exist. + /// + /// You should use [`Ui::interact`] instead. + /// + /// If the widget already exists, its state (sense, Rect, etc) will be updated. + #[allow(clippy::too_many_arguments)] + pub(crate) fn create_widget(&self, w: WidgetRect) -> Response { + // Remember this widget + self.write(|ctx| { + let viewport = ctx.viewport(); + + // We add all widgets here, even non-interactive ones, + // because we need this list not only for checking for blocking widgets, + // but also to know when we have reached the widget we are checking for cover. + viewport.widgets_this_frame.insert(w.layer_id, w); + + if w.sense.focusable { + ctx.memory.interested_in_focus(w.id); + } + }); + + if !w.enabled || !w.sense.focusable || !w.layer_id.allow_interaction() { + // Not interested or allowed input: + self.memory_mut(|mem| mem.surrender_focus(w.id)); + } + + if w.sense.interactive() || w.sense.focusable { + self.check_for_id_clash(w.id, w.rect, "widget"); + } + + #[allow(clippy::let_and_return)] + let res = self.get_response(w); + + #[cfg(feature = "accesskit")] + if w.sense.focusable { + // Make sure anything that can receive focus has an AccessKit node. + // TODO(mwcampbell): For nodes that are filled from widget info, + // some information is written to the node twice. + self.accesskit_node_builder(w.id, |builder| res.fill_accesskit_node_common(builder)); + } + + res + } + + /// Read the response of some widget, which may be called _before_ creating the widget (!). + /// + /// This is because widget interaction happens at the start of the frame, using the previous frame's widgets. + /// + /// If the widget was not visible the previous frame (or this frame), this will return `None`. + pub fn read_response(&self, id: Id) -> Option { + self.write(|ctx| { + let viewport = ctx.viewport(); + viewport + .widgets_this_frame + .get(id) + .or_else(|| viewport.widgets_prev_frame.get(id)) + .copied() + }) + .map(|widget_rect| self.get_response(widget_rect)) + } + + /// Returns `true` if the widget with the given `Id` contains the pointer. + #[deprecated = "Use Response.contains_pointer or Context::read_response instead"] + pub fn widget_contains_pointer(&self, id: Id) -> bool { + self.read_response(id) + .map_or(false, |response| response.contains_pointer) + } + + /// Create scroll delta + /// + /// See [`InputState`], [`ScrollArea`] + pub(crate) fn create_scroll_delta(&self, inner_rect: Rect) { + self.input_mut(|input| { + let pointer_position = input.pointer.interact_pos().unwrap_or_default(); + let is_contains_pointer = inner_rect.contains(pointer_position); + input.create_scroll_delta(false, is_contains_pointer); + }); + } + + /// Do all interaction for an existing widget, without (re-)registering it. + fn get_response(&self, widget_rect: WidgetRect) -> Response { + let WidgetRect { + id, + layer_id, + rect, + interact_rect, + sense, + enabled, + } = widget_rect; + + let highlighted = self.frame_state(|fs| fs.highlight_this_frame.contains(&id)); + + let mut res = Response { + ctx: self.clone(), + layer_id, + id, + rect, + interact_rect, + sense, + enabled, + contains_pointer: false, + hovered: false, + highlighted, + clicked: false, + fake_primary_click: false, + long_touched: false, + drag_started: false, + dragged: false, + drag_stopped: false, + is_pointer_button_down_on: false, + interact_pointer_pos: None, + changed: false, + }; + + self.write(|ctx| { + let viewport = ctx.viewports.entry(ctx.viewport_id()).or_default(); + + res.contains_pointer = viewport.interact_widgets.contains_pointer.contains(&id); + + let input = &viewport.input; + let memory = &mut ctx.memory; + + if enabled + && sense.click + && memory.has_focus(id) + && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) + { + // Space/enter works like a primary click for e.g. selected buttons + res.fake_primary_click = true; + } + + #[cfg(feature = "accesskit")] + if enabled + && sense.click + && input.has_accesskit_action_request(id, accesskit::Action::Default) + { + res.fake_primary_click = true; + } + + if enabled && sense.click && Some(id) == viewport.interact_widgets.long_touched { + res.long_touched = true; + } + + let interaction = memory.interaction(); + + res.is_pointer_button_down_on = interaction.potential_click_id == Some(id) + || interaction.potential_drag_id == Some(id); + + if res.enabled { + res.hovered = viewport.interact_widgets.hovered.contains(&id); + res.dragged = Some(id) == viewport.interact_widgets.dragged; + res.drag_started = Some(id) == viewport.interact_widgets.drag_started; + res.drag_stopped = Some(id) == viewport.interact_widgets.drag_stopped; + } + + let clicked = Some(id) == viewport.interact_widgets.clicked; + let mut any_press = false; + + for pointer_event in &input.pointer.pointer_events { + match pointer_event { + PointerEvent::Moved(_) => {} + PointerEvent::Pressed { .. } => { + any_press = true; + } + PointerEvent::Released { click, .. } => { + if enabled && sense.click && clicked && click.is_some() { + res.clicked = true; + } + + res.is_pointer_button_down_on = false; + res.dragged = false; + } + } + } + + // is_pointer_button_down_on is false when released, but we want interact_pointer_pos + // to still work. + let is_interacted_with = + res.is_pointer_button_down_on || res.long_touched || clicked || res.drag_stopped; + if is_interacted_with { + res.interact_pointer_pos = input.pointer.interact_pos(); + if let (Some(transform), Some(pos)) = ( + memory.layer_transforms.get(&res.layer_id), + &mut res.interact_pointer_pos, + ) { + *pos = transform.inverse() * *pos; + } + } + + if input.pointer.any_down() && !is_interacted_with { + // We don't hover widgets while interacting with *other* widgets: + res.hovered = false; + } + + let pointer_pressed_elsewhere = any_press && !res.hovered; + if pointer_pressed_elsewhere && memory.has_focus(id) { + memory.surrender_focus(id); + } + }); + + res + } + + /// This is called by [`Response::widget_info`], but can also be called directly. + /// + /// With some debug flags it will store the widget info in [`WidgetRects`] for later display. + #[inline] + pub fn register_widget_info(&self, id: Id, make_info: impl Fn() -> crate::WidgetInfo) { + #[cfg(debug_assertions)] + self.write(|ctx| { + if ctx.memory.options.style.debug.show_interactive_widgets { + ctx.viewport().widgets_this_frame.set_info(id, make_info()); + } + }); + + #[cfg(not(debug_assertions))] + { + _ = (self, id, make_info); + } + } + + /// Get a full-screen painter for a new or existing layer + pub fn layer_painter(&self, layer_id: LayerId) -> Painter { + let screen_rect = self.screen_rect(); + Painter::new(self.clone(), layer_id, screen_rect) + } + + /// Paint on top of everything else + pub fn debug_painter(&self) -> Painter { + Self::layer_painter(self, LayerId::debug()) + } + + /// Print this text next to the cursor at the end of the frame. + /// + /// If you call this multiple times, the text will be appended. + /// + /// This only works if compiled with `debug_assertions`. + /// + /// ``` + /// # let ctx = egui::Context::default(); + /// # let state = true; + /// ctx.debug_text(format!("State: {state:?}")); + /// ``` + /// + /// This is just a convenience for calling [`crate::debug_text::print`]. + #[track_caller] + pub fn debug_text(&self, text: impl Into) { + crate::debug_text::print(self, text); + } + + /// What operating system are we running on? + /// + /// When compiling natively, this is + /// figured out from the `target_os`. + /// + /// For web, this can be figured out from the user-agent, + /// and is done so by [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe). + pub fn os(&self) -> OperatingSystem { + self.read(|ctx| ctx.os) + } + + /// Set the operating system we are running on. + /// + /// If you are writing wasm-based integration for egui you + /// may want to set this based on e.g. the user-agent. + pub fn set_os(&self, os: OperatingSystem) { + self.write(|ctx| ctx.os = os); + } + + /// Set the cursor icon. + /// + /// Equivalent to: + /// ``` + /// # let ctx = egui::Context::default(); + /// ctx.output_mut(|o| o.cursor_icon = egui::CursorIcon::PointingHand); + /// ``` + pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) { + self.output_mut(|o| o.cursor_icon = cursor_icon); + } + + /// Open an URL in a browser. + /// + /// Equivalent to: + /// ``` + /// # let ctx = egui::Context::default(); + /// # let open_url = egui::OpenUrl::same_tab("http://www.example.com"); + /// ctx.output_mut(|o| o.open_url = Some(open_url)); + /// ``` + pub fn open_url(&self, open_url: crate::OpenUrl) { + self.output_mut(|o| o.open_url = Some(open_url)); + } + + /// Copy the given text to the system clipboard. + /// + /// Empty strings are ignored. + /// + /// Equivalent to: + /// ``` + /// # let ctx = egui::Context::default(); + /// ctx.output_mut(|o| o.copied_text = "Copy this".to_owned()); + /// ``` + pub fn copy_text(&self, text: String) { + self.output_mut(|o| o.copied_text = text); + } + + /// Format the given shortcut in a human-readable way (e.g. `Ctrl+Shift+X`). + /// + /// Can be used to get the text for [`Button::shortcut_text`]. + pub fn format_shortcut(&self, shortcut: &KeyboardShortcut) -> String { + let os = self.os(); + + let is_mac = matches!(os, OperatingSystem::Mac | OperatingSystem::IOS); + + let can_show_symbols = || { + let ModifierNames { + alt, + ctrl, + shift, + mac_cmd, + .. + } = ModifierNames::SYMBOLS; + + let font_id = TextStyle::Body.resolve(&self.style()); + self.fonts(|f| { + let mut lock = f.lock(); + let font = lock.fonts.font(&font_id); + font.has_glyphs(alt) + && font.has_glyphs(ctrl) + && font.has_glyphs(shift) + && font.has_glyphs(mac_cmd) + }) + }; + + if is_mac && can_show_symbols() { + shortcut.format(&ModifierNames::SYMBOLS, is_mac) + } else { + shortcut.format(&ModifierNames::NAMES, is_mac) + } + } + + /// The current frame number for the current viewport. + /// + /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_frame`]. + /// + /// Between calls to [`Self::run`], this is the frame number of the coming frame. + pub fn frame_nr(&self) -> u64 { + self.frame_nr_for(self.viewport_id()) + } + + /// The current frame number. + /// + /// Starts at zero, and is incremented at the end of [`Self::run`] or by [`Self::end_frame`]. + /// + /// Between calls to [`Self::run`], this is the frame number of the coming frame. + pub fn frame_nr_for(&self, id: ViewportId) -> u64 { + self.read(|ctx| ctx.viewports.get(&id).map_or(0, |v| v.repaint.frame_nr)) + } + + /// 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. + /// + /// To request repaint with a delay, use [`Self::request_repaint_after`]. + /// + /// If called from outside the UI thread, the UI thread will wake up and run, + /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] + /// (this will work on `eframe`). + /// + /// This will repaint the current viewport. + #[track_caller] + pub fn request_repaint(&self) { + self.request_repaint_of(self.viewport_id()); + } + + /// 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. + /// + /// To request repaint with a delay, use [`Self::request_repaint_after_for`]. + /// + /// If called from outside the UI thread, the UI thread will wake up and run, + /// provided the egui integration has set that up via [`Self::set_request_repaint_callback`] + /// (this will work on `eframe`). + /// + /// This will repaint the specified viewport. + #[track_caller] + pub fn request_repaint_of(&self, id: ViewportId) { + let cause = RepaintCause::new(); + self.write(|ctx| ctx.request_repaint(id, cause)); + } + + /// Request repaint after at most the specified duration elapses. + /// + /// The backend can chose to repaint sooner, for instance if some other code called + /// this method with a lower duration, or if new events arrived. + /// + /// The function can be multiple times, but only the *smallest* duration will be considered. + /// So, if the function is called two times with `1 second` and `2 seconds`, egui will repaint + /// after `1 second` + /// + /// This is primarily useful for applications who would like to save battery by avoiding wasted + /// redraws when the app is not in focus. But sometimes the GUI of the app might become stale + /// and outdated if it is not updated for too long. + /// + /// Let's say, something like a stopwatch widget that displays the time in seconds. You would waste + /// resources repainting multiple times within the same second (when you have no input), + /// just calculate the difference of duration between current time and next second change, + /// and call this function, to make sure that you are displaying the latest updated time, but + /// not wasting resources on needless repaints within the same second. + /// + /// ### Quirk: + /// Duration begins at the next frame. Let's say for example that it's a very inefficient app + /// and takes 500 milliseconds per frame at 2 fps. The widget / user might want a repaint in + /// next 500 milliseconds. Now, app takes 1000 ms per frame (1 fps) because the backend event + /// timeout takes 500 milliseconds AFTER the vsync swap buffer. + /// So, it's not that we are requesting repaint within X duration. We are rather timing out + /// during app idle time where we are not receiving any new input events. + /// + /// This repaints the current viewport + #[track_caller] + pub fn request_repaint_after(&self, duration: Duration) { + self.request_repaint_after_for(duration, self.viewport_id()); + } + + /// Request repaint after at most the specified duration elapses. + /// + /// The backend can chose to repaint sooner, for instance if some other code called + /// this method with a lower duration, or if new events arrived. + /// + /// The function can be multiple times, but only the *smallest* duration will be considered. + /// So, if the function is called two times with `1 second` and `2 seconds`, egui will repaint + /// after `1 second` + /// + /// This is primarily useful for applications who would like to save battery by avoiding wasted + /// redraws when the app is not in focus. But sometimes the GUI of the app might become stale + /// and outdated if it is not updated for too long. + /// + /// Let's say, something like a stopwatch widget that displays the time in seconds. You would waste + /// resources repainting multiple times within the same second (when you have no input), + /// just calculate the difference of duration between current time and next second change, + /// and call this function, to make sure that you are displaying the latest updated time, but + /// not wasting resources on needless repaints within the same second. + /// + /// ### Quirk: + /// Duration begins at the next frame. Let's say for example that it's a very inefficient app + /// and takes 500 milliseconds per frame at 2 fps. The widget / user might want a repaint in + /// next 500 milliseconds. Now, app takes 1000 ms per frame (1 fps) because the backend event + /// timeout takes 500 milliseconds AFTER the vsync swap buffer. + /// So, it's not that we are requesting repaint within X duration. We are rather timing out + /// during app idle time where we are not receiving any new input events. + /// + /// This repaints the specified viewport + #[track_caller] + pub fn request_repaint_after_for(&self, duration: Duration, id: ViewportId) { + let cause = RepaintCause::new(); + self.write(|ctx| ctx.request_repaint_after(duration, id, cause)); + } + + /// Was a repaint requested last frame for the current viewport? + #[must_use] + pub fn requested_repaint_last_frame(&self) -> bool { + self.requested_repaint_last_frame_for(&self.viewport_id()) + } + + /// Was a repaint requested last frame for the given viewport? + #[must_use] + pub fn requested_repaint_last_frame_for(&self, viewport_id: &ViewportId) -> bool { + self.read(|ctx| ctx.requested_immediate_repaint_prev_frame(viewport_id)) + } + + /// Has a repaint been requested for the current viewport? + #[must_use] + pub fn has_requested_repaint(&self) -> bool { + self.has_requested_repaint_for(&self.viewport_id()) + } + + /// Has a repaint been requested for the given viewport? + #[must_use] + pub fn has_requested_repaint_for(&self, viewport_id: &ViewportId) -> bool { + self.read(|ctx| ctx.has_requested_repaint(viewport_id)) + } + + /// Why are we repainting? + /// + /// This can be helpful in debugging why egui is constantly repainting. + pub fn repaint_causes(&self) -> Vec { + self.read(|ctx| { + ctx.viewports + .get(&ctx.viewport_id()) + .map(|v| v.repaint.prev_causes.clone()) + }) + .unwrap_or_default() + } + + /// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`] or [`Self::request_repaint_after`]. + /// + /// This lets you wake up a sleeping UI thread. + /// + /// Note that only one callback can be set. Any new call overrides the previous callback. + pub fn set_request_repaint_callback( + &self, + callback: impl Fn(RequestRepaintInfo) + Send + Sync + 'static, + ) { + let callback = Box::new(callback); + self.write(|ctx| ctx.request_repaint_callback = Some(callback)); + } +} + +/// Callbacks +impl Context { + /// Call the given callback at the start of each frame + /// of each viewport. + /// + /// This can be used for egui _plugins_. + /// See [`crate::debug_text`] for an example. + pub fn on_begin_frame(&self, debug_name: &'static str, cb: ContextCallback) { + let named_cb = NamedContextCallback { + debug_name, + callback: cb, + }; + self.write(|ctx| ctx.plugins.on_begin_frame.push(named_cb)); + } + + /// Call the given callback at the end of each frame + /// of each viewport. + /// + /// This can be used for egui _plugins_. + /// See [`crate::debug_text`] for an example. + pub fn on_end_frame(&self, debug_name: &'static str, cb: ContextCallback) { + let named_cb = NamedContextCallback { + debug_name, + callback: cb, + }; + self.write(|ctx| ctx.plugins.on_end_frame.push(named_cb)); + } +} + +impl Context { + /// 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) { + crate::profile_function!(); + + let pixels_per_point = self.pixels_per_point(); + + let mut update_fonts = true; + + self.read(|ctx| { + if let Some(current_fonts) = ctx.fonts.get(&pixels_per_point.into()) { + // NOTE: this comparison is expensive since it checks TTF data for equality + if current_fonts.lock().fonts.definitions() == &font_definitions { + update_fonts = false; // no need to update + } + } + }); + + if update_fonts { + self.memory_mut(|mem| mem.new_font_definitions = Some(font_definitions)); + } + } + + /// The [`Style`] used by all subsequent windows, panels etc. + pub fn style(&self) -> Arc