#![warn(missing_docs)] // Let's keep `Context` well-documented. use std::{borrow::Cow, cell::RefCell, panic::Location, sync::Arc, time::Duration}; use containers::area::AreaState; use epaint::{ emath::{self, TSTransform}, mutex::RwLock, pos2, stats::PaintStats, tessellator, text::Fonts, util::OrderedFloat, vec2, ClippedPrimitive, ClippedShape, Color32, ImageData, ImageDelta, Pos2, Rect, TessellationOptions, TextureAtlas, TextureId, Vec2, }; use crate::{ animation_manager::AnimationManager, containers, data::output::PlatformOutput, epaint, hit_test, input_state::{InputState, MultiTouchInfo, PointerEvent}, interaction, layers::GraphicLayers, load, load::{Bytes, Loaders, SizedTexture}, memory::{Options, Theme}, menu, os::OperatingSystem, output::FullOutput, pass_state::PassState, resize, scroll_area, util::IdTypeMap, viewport::ViewportClass, Align2, CursorIcon, DeferredViewportUiCallback, FontDefinitions, Grid, Id, ImmediateViewport, ImmediateViewportRendererCallback, Key, KeyboardShortcut, Label, LayerId, Memory, ModifierNames, NumExt, Order, Painter, RawInput, Response, RichText, ScrollArea, Sense, Style, TextStyle, TextureHandle, TextureOptions, Ui, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput, Widget, WidgetRect, WidgetText, }; #[cfg(feature = "accesskit")] use crate::IdMap; 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 number of fully completed passes, of the entire lifetime of the [`Context`]. /// /// This can be compared to [`Context::cumulative_pass_nr`] to see if we we still /// need another repaint (ui pass / frame), or if one has already happened. pub current_cumulative_pass_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)] struct Plugins { pub on_begin_pass: Vec, pub on_end_pass: 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_pass(&self, ctx: &Context) { Self::call(ctx, "on_begin_pass", &self.on_begin_pass); } fn on_end_pass(&self, ctx: &Context) { Self::call(ctx, "on_end_pass", &self.on_end_pass); } } // ---------------------------------------------------------------------------- /// Repaint-logic impl ContextImpl { /// This is where we update the repaint logic. fn begin_pass_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_pass_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_cumulative_pass_nr: viewport.repaint.cumulative_pass_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, mut 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. } if let Ok(predicted_frame_time) = Duration::try_from_secs_f32(viewport.input.predicted_dt) { // Make it less likely we over-shoot the target: delay = delay.saturating_sub(predicted_frame_time); } 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_cumulative_pass_nr: viewport.repaint.cumulative_pass_nr, }); } } } #[must_use] fn requested_immediate_repaint_prev_pass(&self, viewport_id: &ViewportId) -> bool { self.viewports .get(viewport_id) .map_or(false, |v| v.repaint.requested_immediate_repaint_prev_pass()) } #[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. /// /// Mostly for internal use. /// Things here may move and change without warning. #[derive(Default)] pub struct ViewportState { /// The type of viewport. /// /// This will never be [`ViewportClass::Embedded`], /// since those don't result in real viewports. pub class: ViewportClass, /// The latest delta pub builder: ViewportBuilder, /// The user-code that shows the GUI, used for deferred viewports. /// /// `None` for immediate viewports. pub viewport_ui_cb: Option>, pub input: InputState, /// State that is collected during a pass and then cleared. pub this_pass: PassState, /// The final [`PassState`] from last pass. /// /// Only read from. pub prev_pass: PassState, /// Has this viewport been updated this pass? pub used: bool, /// State related to repaint scheduling. repaint: ViewportRepaintInfo, // ---------------------- // Updated at the start of the pass: // /// Which widgets are under the pointer? pub hits: WidgetHits, /// What widgets are being interacted with this pass? /// /// Based on the widgets from last pass, and input in this pass. pub interact_widgets: InteractionSnapshot, // ---------------------- // The output of a pass: // pub graphics: GraphicLayers, // Most of the things in `PlatformOutput` are not actually viewport dependent. pub output: PlatformOutput, pub commands: Vec, // ---------------------- // Cross-frame statistics: pub num_multipass_in_row: usize, } /// What called [`Context::request_repaint`] or [`Context::request_discard`]? #[derive(Clone, PartialEq, Eq, Hash)] pub struct RepaintCause { /// What file had the call that requested the repaint? pub file: &'static str, /// What line number of the call that requested the repaint? pub line: u32, /// Explicit reason; human readable. pub reason: Cow<'static, str>, } impl std::fmt::Debug for RepaintCause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{} {}", self.file, self.line, self.reason) } } impl std::fmt::Display for RepaintCause { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{} {}", self.file, self.line, self.reason) } } 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(), reason: "".into(), } } /// Capture the file and line number of the call site, /// as well as add a reason. #[allow(clippy::new_without_default)] #[track_caller] pub fn new_reason(reason: impl Into>) -> Self { let caller = Location::caller(); Self { file: caller.file(), line: caller.line(), reason: reason.into(), } } } /// Per-viewport state related to repaint scheduling. struct ViewportRepaintInfo { /// Monotonically increasing counter. cumulative_pass_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 pass. /// /// This is also returned in [`crate::ViewportOutput`]. repaint_delay: Duration, /// While positive, keep requesting repaints. Decrement at the start of each pass. outstanding: u8, /// What caused repaints during this pass? causes: Vec, /// What triggered a repaint the previous pass? /// (i.e: why are we updating now?) prev_causes: Vec, /// What was the output of `repaint_delay` on the previous pass? /// /// If this was zero, we are repainting as quickly as possible /// (as far as we know). prev_pass_paint_delay: Duration, } impl Default for ViewportRepaintInfo { fn default() -> Self { Self { cumulative_pass_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_pass_paint_delay: Duration::MAX, } } } impl ViewportRepaintInfo { pub fn requested_immediate_repaint_prev_pass(&self) -> bool { self.prev_pass_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 pass, becomes active at the start of the next pass. 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, loaders: Arc, } impl ContextImpl { fn begin_pass(&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_pass_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_pass(&new_raw_input, &all_viewport_ids); viewport.input = std::mem::take(&mut viewport.input).begin_pass( new_raw_input, viewport.repaint.requested_immediate_repaint_prev_pass(), pixels_per_point, &self.memory.options, ); let screen_rect = viewport.input.screen_rect; viewport.this_pass.begin_pass(screen_rect); { let area_order = self.memory.areas().order_map(); let mut layers: Vec = viewport.prev_pass.widgets.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.prev_pass.widgets, &layers, &self.memory.layer_transforms, pos, interact_radius, ) } else { WidgetHits::default() }; viewport.interact_widgets = crate::interaction::interact( &viewport.interact_widgets, &viewport.prev_pass.widgets, &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(), AreaState { pivot_pos: Some(screen_rect.left_top()), pivot: Align2::LEFT_TOP, size: Some(screen_rect.size()), interactable: true, last_became_visible_at: None, }, ); #[cfg(feature = "accesskit")] if self.is_accesskit_enabled { crate::profile_scope!("accesskit"); use crate::pass_state::AccessKitPassState; 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.this_pass.accesskit_state = Some(AccessKitPassState { 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::trace!("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_pass"); fonts.begin_pass(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().this_pass.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) } 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 1. /// /// At most [`Options::max_passes`] calls will be issued to `run_ui`, /// and only on the rare occasion that [`Context::request_discard`] is called. /// Usually, it `run_ui` will only be called once. /// /// Put your widgets into a [`crate::SidePanel`], [`crate::TopBottomPanel`], [`crate::CentralPanel`], [`crate::Window`] or [`crate::Area`]. /// /// Instead of calling `run`, you can alternatively use [`Self::begin_pass`] and [`Context::end_pass`]. /// /// ``` /// // 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, mut new_input: RawInput, mut run_ui: impl FnMut(&Self)) -> FullOutput { crate::profile_function!(); let viewport_id = new_input.viewport_id; let max_passes = self.write(|ctx| ctx.memory.options.max_passes.get()); let mut output = FullOutput::default(); debug_assert_eq!(output.platform_output.num_completed_passes, 0); loop { crate::profile_scope!( "pass", output.platform_output.num_completed_passes.to_string() ); // We must move the `num_passes` (back) to the viewport output so that [`Self::will_discard`] // has access to the latest pass count. self.write(|ctx| { let viewport = ctx.viewport_for(viewport_id); viewport.output.num_completed_passes = std::mem::take(&mut output.platform_output.num_completed_passes); output.platform_output.request_discard_reasons.clear(); }); self.begin_pass(new_input.take()); run_ui(self); output.append(self.end_pass()); debug_assert!(0 < output.platform_output.num_completed_passes); if !output.platform_output.requested_discard() { break; // no need for another pass } if max_passes <= output.platform_output.num_completed_passes { #[cfg(feature = "log")] log::debug!("Ignoring call request_discard, because max_passes={max_passes}. Requested from {:?}", output.platform_output.request_discard_reasons); break; } } self.write(|ctx| { let did_multipass = 1 < output.platform_output.num_completed_passes; let viewport = ctx.viewport_for(viewport_id); if did_multipass { viewport.num_multipass_in_row += 1; } else { viewport.num_multipass_in_row = 0; } }); output } /// An alternative to calling [`Self::run`]. /// /// It is usually better to use [`Self::run`], because /// `run` supports multi-pass layout using [`Self::request_discard`]. /// /// ``` /// // One egui context that you keep reusing: /// let mut ctx = egui::Context::default(); /// /// // Each frame: /// let input = egui::RawInput::default(); /// ctx.begin_pass(input); /// /// egui::CentralPanel::default().show(&ctx, |ui| { /// ui.label("Hello egui!"); /// }); /// /// let full_output = ctx.end_pass(); /// // handle full_output /// ``` pub fn begin_pass(&self, new_input: RawInput) { crate::profile_function!(); self.write(|ctx| ctx.begin_pass(new_input)); // Plugins run just after the pass starts: self.read(|ctx| ctx.plugins.clone()).on_begin_pass(self); } /// See [`Self::begin_pass`]. #[deprecated = "Renamed begin_pass"] pub fn begin_frame(&self, new_input: RawInput) { self.begin_pass(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 pass and 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 [`PassState`]. /// /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { self.write(move |ctx| reader(&ctx.viewport().this_pass)) } /// Read-write access to [`PassState`]. /// /// This is only valid during the call to [`Self::run`] (between [`Self::begin_pass`] and [`Self::end_pass`]). #[inline] pub(crate) fn pass_state_mut(&self, writer: impl FnOnce(&mut PassState) -> R) -> R { self.write(move |ctx| writer(&mut ctx.viewport().this_pass)) } /// Read-only access to the [`PassState`] from the previous pass. /// /// This is swapped at the end of each pass. #[inline] pub(crate) fn prev_pass_state(&self, reader: impl FnOnce(&PassState) -> R) -> R { self.write(move |ctx| reader(&ctx.viewport().prev_pass)) } /// 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 pass 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.pass_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_salt: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_focus` should usually be true, unless you call this function multiple times with the /// same widget, then `allow_focus` should only be true once (like in [`Ui::new`] (true) and [`Ui::remember_min_rect`] (false)). #[allow(clippy::too_many_arguments)] pub(crate) fn create_widget(&self, w: WidgetRect, allow_focus: bool) -> 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.this_pass.widgets.insert(w.layer_id, w); if allow_focus && w.sense.focusable { ctx.memory.interested_in_focus(w.id); } }); if allow_focus && (!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 allow_focus && 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 pass, using the widget rects from the previous pass. /// /// If the widget was not visible the previous pass (or this pass), this will return `None`. pub fn read_response(&self, id: Id) -> Option { self.write(|ctx| { let viewport = ctx.viewport(); viewport .this_pass .widgets .get(id) .or_else(|| viewport.prev_pass.widgets.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) } /// Do all interaction for an existing widget, without (re-)registering it. pub(crate) fn get_response(&self, widget_rect: WidgetRect) -> Response { let WidgetRect { id, layer_id, rect, interact_rect, sense, enabled, } = widget_rect; // previous pass + "highlight next pass" == "highlight this pass" let highlighted = self.prev_pass_state(|fs| fs.highlight_next_pass.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, intrinsic_size: None, }; 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 [`crate::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().this_pass.widgets.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 pass. /// /// 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 [`crate::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 total number of completed passes (usually there is one pass per rendered frame). /// /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). pub fn cumulative_pass_nr(&self) -> u64 { self.cumulative_pass_nr_for(self.viewport_id()) } /// The total number of completed passes (usually there is one pass per rendered frame). /// /// Starts at zero, and is incremented for each completed pass inside of [`Self::run`] (usually once). pub fn cumulative_pass_nr_for(&self, id: ViewportId) -> u64 { self.read(|ctx| { ctx.viewports .get(&id) .map_or(0, |v| v.repaint.cumulative_pass_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()); } /// Repaint after this many seconds. /// /// See [`Self::request_repaint_after`] for details. #[track_caller] pub fn request_repaint_after_secs(&self, seconds: f32) { if let Ok(duration) = std::time::Duration::try_from_secs_f32(seconds) { self.request_repaint_after(duration); } } /// 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 pass for the current viewport? #[must_use] pub fn requested_repaint_last_pass(&self) -> bool { self.requested_repaint_last_pass_for(&self.viewport_id()) } /// Was a repaint requested last pass for the given viewport? #[must_use] pub fn requested_repaint_last_pass_for(&self, viewport_id: &ViewportId) -> bool { self.read(|ctx| ctx.requested_immediate_repaint_prev_pass(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)); } /// Request to discard the visual output of this pass, /// and to immediately do another one. /// /// This can be called to cover up visual glitches during a "sizing pass". /// For instance, when a [`crate::Grid`] is first shown we don't yet know the /// width and heights of its columns and rows. egui will do a best guess, /// but it will likely be wrong. Next pass it can read the sizes from the previous /// pass, and from there on the widths will be stable. /// This means the first pass will look glitchy, and ideally should not be shown to the user. /// So [`crate::Grid`] calls [`Self::request_discard`] to cover up this glitches. /// /// There is a limit to how many passes egui will perform, set by [`Options::max_passes`]. /// Therefore, the request might be declined. /// /// You can check if the current pass will be discarded with [`Self::will_discard`]. /// /// You should be very conservative with when you call [`Self::request_discard`], /// as it will cause an extra ui pass, potentially leading to extra CPU use and frame judder. /// /// The given reason should be a human-readable string that explains why `request_discard` /// was called. This will be shown in certain debug situations, to help you figure out /// why a pass was discarded. #[track_caller] pub fn request_discard(&self, reason: impl Into>) { let cause = RepaintCause::new_reason(reason); self.output_mut(|o| o.request_discard_reasons.push(cause)); #[cfg(feature = "log")] log::trace!( "request_discard: {}", if self.will_discard() { "allowed" } else { "denied" } ); } /// Will the visual output of this pass be discarded? /// /// If true, you can early-out from expensive graphics operations. /// /// See [`Self::request_discard`] for more. pub fn will_discard(&self) -> bool { self.write(|ctx| { let vp = ctx.viewport(); // NOTE: `num_passes` is incremented vp.output.requested_discard() && vp.output.num_completed_passes + 1 < ctx.memory.options.max_passes.get() }) } } /// Callbacks impl Context { /// Call the given callback at the start of each pass of each viewport. /// /// This can be used for egui _plugins_. /// See [`crate::debug_text`] for an example. pub fn on_begin_pass(&self, debug_name: &'static str, cb: ContextCallback) { let named_cb = NamedContextCallback { debug_name, callback: cb, }; self.write(|ctx| ctx.plugins.on_begin_pass.push(named_cb)); } /// Call the given callback at the end of each pass of each viewport. /// /// This can be used for egui _plugins_. /// See [`crate::debug_text`] for an example. pub fn on_end_pass(&self, debug_name: &'static str, cb: ContextCallback) { let named_cb = NamedContextCallback { debug_name, callback: cb, }; self.write(|ctx| ctx.plugins.on_end_pass.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 pass. 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)); } } /// Does the OS use dark or light mode? /// This is used when the theme preference is set to [`crate::ThemePreference::System`]. pub fn system_theme(&self) -> Option { self.memory(|mem| mem.options.system_theme) } /// The [`Theme`] used to select the appropriate [`Style`] (dark or light) /// used by all subsequent windows, panels etc. pub fn theme(&self) -> Theme { self.options(|opt| opt.theme()) } /// The [`Theme`] used to select between dark and light [`Self::style`] /// as the active style used by all subsequent windows, panels etc. /// /// Example: /// ``` /// # let mut ctx = egui::Context::default(); /// ctx.set_theme(egui::Theme::Light); // Switch to light mode /// ``` pub fn set_theme(&self, theme_preference: impl Into) { self.options_mut(|opt| opt.theme_preference = theme_preference.into()); } /// The currently active [`Style`] used by all subsequent windows, panels etc. pub fn style(&self) -> Arc