// #![warn(missing_docs)] use std::sync::Arc; use crate::{ animation_manager::AnimationManager, data::output::PlatformOutput, frame_state::FrameState, input_state::*, layers::GraphicLayers, memory::Options, output::FullOutput, TextureHandle, *, }; use epaint::{mutex::*, stats::*, text::Fonts, TessellationOptions, *}; // ---------------------------------------------------------------------------- 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(), ); assert_eq!(font_id, TextureId::default()); Self(Arc::new(RwLock::new(tex_mngr))) } } // ---------------------------------------------------------------------------- #[derive(Default)] struct ContextImpl { /// `None` until the start of the first frame. fonts: Option, memory: Memory, animation_manager: AnimationManager, tex_manager: WrappedTextureManager, input: InputState, /// State that is collected during a frame and then cleared frame_state: FrameState, // The output of a frame: graphics: GraphicLayers, output: PlatformOutput, paint_stats: PaintStats, /// While positive, keep requesting repaints. Decrement at the end of each frame. repaint_requests: u32, request_repaint_callbacks: Option>, requested_repaint_last_frame: bool, } impl ContextImpl { fn begin_frame_mut(&mut self, new_raw_input: RawInput) { self.memory.begin_frame(&self.input, &new_raw_input); self.input = std::mem::take(&mut self.input) .begin_frame(new_raw_input, self.requested_repaint_last_frame); if let Some(new_pixels_per_point) = self.memory.new_pixels_per_point.take() { self.input.pixels_per_point = new_pixels_per_point; } self.frame_state.begin_frame(&self.input); self.update_fonts_mut(); // Ensure we register the background area so panels and background ui can catch clicks: let screen_rect = self.input.screen_rect(); self.memory.areas.set_state( LayerId::background(), containers::area::State { pos: screen_rect.min, size: screen_rect.size(), interactable: true, }, ); } /// Load fonts unless already loaded. fn update_fonts_mut(&mut self) { let pixels_per_point = self.input.pixels_per_point(); let max_texture_side = self.input.max_texture_side; if let Some(font_definitions) = self.memory.new_font_definitions.take() { let fonts = Fonts::new(pixels_per_point, max_texture_side, font_definitions); self.fonts = Some(fonts); } let fonts = self.fonts.get_or_insert_with(|| { let font_definitions = FontDefinitions::default(); Fonts::new(pixels_per_point, max_texture_side, font_definitions) }); fonts.begin_frame(pixels_per_point, max_texture_side); if self.memory.options.preload_font_glyphs { // Preload the most common characters for the most common fonts. // This is not very important to do, but may a few GPU operations. for font_id in self.memory.options.style.text_styles.values() { fonts.lock().fonts.font(font_id).preload_common_characters(); } } } } // ---------------------------------------------------------------------------- /// 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). /// /// All methods are marked `&self`; [`Context`] has interior mutability (protected by a mutex). /// /// /// You can store /// /// # Example: /// /// ``` no_run /// # fn handle_platform_output(_: egui::PlatformOutput) {} /// # fn paint(textures_detla: 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); // create triangles to paint /// paint(full_output.textures_delta, clipped_primitives); /// } /// ``` #[derive(Clone)] pub struct Context(Arc>); impl std::cmp::PartialEq for Context { fn eq(&self, other: &Context) -> bool { Arc::ptr_eq(&self.0, &other.0) } } impl Default for Context { fn default() -> Self { Self(Arc::new(RwLock::new(ContextImpl { // Start with painting an extra frame to compensate for some widgets // that take two frames before they "settle": repaint_requests: 1, ..ContextImpl::default() }))) } } impl Context { fn read(&self) -> RwLockReadGuard<'_, ContextImpl> { self.0.read() } fn write(&self) -> RwLockWriteGuard<'_, ContextImpl> { 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(&Context)) -> FullOutput { 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) { self.write().begin_frame_mut(new_input); } // --------------------------------------------------------------------- /// 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().used_ids.insert(id, new_rect); if let Some(prev_rect) = prev_rect { // it is ok to reuse the same ID for e.g. a frame around a widget, // or to check for interaction with the same widget twice: if prev_rect.expand(0.1).contains_rect(new_rect) || new_rect.expand(0.1).contains_rect(prev_rect) { return; } let show_error = |pos: Pos2, text: String| { let painter = self.debug_painter(); let rect = painter.error(pos, text); if let Some(pointer_pos) = self.pointer_hover_pos() { if rect.contains(pointer_pos) { painter.error( rect.left_bottom() + vec2(2.0, 4.0), "ID clashes happens when things like Windows or CollapsingHeaders share names,\n\ or when things like Plot and Grid:s aren't given unique id_source:s.\n\n\ Sometimes the solution is to use ui.push_id.", ); } } }; let id_str = id.short_debug_format(); if prev_rect.min.distance(new_rect.min) < 4.0 { show_error( new_rect.min, format!("Double use of {} ID {}", what, id_str), ); } else { show_error( prev_rect.min, format!("First use of {} ID {}", what, id_str), ); show_error( new_rect.min, format!("Second use of {} ID {}", what, id_str), ); } } } // --------------------------------------------------------------------- /// Use `ui.interact` instead #[allow(clippy::too_many_arguments)] pub(crate) fn interact( &self, clip_rect: Rect, item_spacing: Vec2, layer_id: LayerId, id: Id, rect: Rect, sense: Sense, enabled: bool, ) -> Response { let gap = 0.5; // Just to make sure we don't accidentally hover two things at once (a small eps should be sufficient). // Make it easier to click things: let interact_rect = rect.expand2( (0.5 * item_spacing - Vec2::splat(gap)) .at_least(Vec2::splat(0.0)) .at_most(Vec2::splat(5.0)), ); // make it easier to click let hovered = self.rect_contains_pointer(layer_id, clip_rect.intersect(interact_rect)); self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered) } /// You specify if a thing is hovered, and the function gives a [`Response`]. pub(crate) fn interact_with_hovered( &self, layer_id: LayerId, id: Id, rect: Rect, sense: Sense, enabled: bool, hovered: bool, ) -> Response { let hovered = hovered && enabled; // can't even hover disabled widgets let mut response = Response { ctx: self.clone(), layer_id, id, rect, sense, enabled, hovered, clicked: Default::default(), double_clicked: Default::default(), triple_clicked: Default::default(), dragged: false, drag_released: false, is_pointer_button_down_on: false, interact_pointer_pos: None, changed: false, // must be set by the widget itself }; if !enabled || !sense.focusable || !layer_id.allow_interaction() { // Not interested or allowed input: self.memory().surrender_focus(id); return response; } self.check_for_id_clash(id, rect, "widget"); let clicked_elsewhere = response.clicked_elsewhere(); let ctx_impl = &mut *self.write(); let memory = &mut ctx_impl.memory; let input = &mut ctx_impl.input; // We only want to focus labels if the screen reader is on. let interested_in_focus = sense.interactive() || sense.focusable && memory.options.screen_reader; if interested_in_focus { memory.interested_in_focus(id); } if sense.click && memory.has_focus(response.id) && (input.key_pressed(Key::Space) || input.key_pressed(Key::Enter)) { // Space/enter works like a primary click for e.g. selected buttons response.clicked[PointerButton::Primary as usize] = true; } if sense.click || sense.drag { memory.interaction.click_interest |= hovered && sense.click; memory.interaction.drag_interest |= hovered && sense.drag; response.dragged = memory.interaction.drag_id == Some(id); response.is_pointer_button_down_on = memory.interaction.click_id == Some(id) || response.dragged; for pointer_event in &input.pointer.pointer_events { match pointer_event { PointerEvent::Moved(_) => {} PointerEvent::Pressed { .. } => { if hovered { if sense.click && memory.interaction.click_id.is_none() { // potential start of a click memory.interaction.click_id = Some(id); response.is_pointer_button_down_on = true; } // HACK: windows have low priority on dragging. // This is so that if you drag a slider in a window, // the slider will steal the drag away from the window. // This is needed because we do window interaction first (to prevent frame delay), // and then do content layout. if sense.drag && (memory.interaction.drag_id.is_none() || memory.interaction.drag_is_window) { // potential start of a drag memory.interaction.drag_id = Some(id); memory.interaction.drag_is_window = false; memory.window_interaction = None; // HACK: stop moving windows (if any) response.is_pointer_button_down_on = true; response.dragged = true; } } } PointerEvent::Released(click) => { response.drag_released = response.dragged; response.dragged = false; if hovered && response.is_pointer_button_down_on { if let Some(click) = click { let clicked = hovered && response.is_pointer_button_down_on; response.clicked[click.button as usize] = clicked; response.double_clicked[click.button as usize] = clicked && click.is_double(); response.triple_clicked[click.button as usize] = clicked && click.is_triple(); } } } } } } if response.is_pointer_button_down_on { response.interact_pointer_pos = input.pointer.interact_pos(); } if input.pointer.any_down() { response.hovered &= response.is_pointer_button_down_on; // we don't hover widgets while interacting with *other* widgets } if memory.has_focus(response.id) && clicked_elsewhere { memory.surrender_focus(id); } if response.dragged() && !memory.has_focus(response.id) { // e.g.: remove focus from a widget when you drag something else memory.stop_text_input(); } response } /// Get a full-screen painter for a new or existing layer pub fn layer_painter(&self, layer_id: LayerId) -> Painter { let screen_rect = self.input().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()) } /// How much space is still available after panels has been added. /// This is the "background" area, what egui doesn't cover with panels (but may cover with windows). /// This is also the area to which windows are constrained. pub fn available_rect(&self) -> Rect { self.frame_state().available_rect() } } /// ## Borrows parts of [`Context`] impl Context { /// Stores all the egui state. /// /// If you want to store/restore egui, serialize this. #[inline] pub fn memory(&self) -> RwLockWriteGuard<'_, Memory> { RwLockWriteGuard::map(self.write(), |c| &mut c.memory) } /// Stores superficial widget state. #[inline] pub fn data(&self) -> RwLockWriteGuard<'_, crate::util::IdTypeMap> { RwLockWriteGuard::map(self.write(), |c| &mut c.memory.data) } #[inline] pub(crate) fn graphics(&self) -> RwLockWriteGuard<'_, GraphicLayers> { RwLockWriteGuard::map(self.write(), |c| &mut c.graphics) } /// What egui outputs each frame. /// /// ``` /// # let mut ctx = egui::Context::default(); /// ctx.output().cursor_icon = egui::CursorIcon::Progress; /// ``` #[inline] pub fn output(&self) -> RwLockWriteGuard<'_, PlatformOutput> { RwLockWriteGuard::map(self.write(), |c| &mut c.output) } #[inline] pub(crate) fn frame_state(&self) -> RwLockWriteGuard<'_, FrameState> { RwLockWriteGuard::map(self.write(), |c| &mut c.frame_state) } /// Access the [`InputState`]. /// /// Note that this locks the [`Context`], so be careful with if-let bindings: /// /// ``` /// # let mut ctx = egui::Context::default(); /// if let Some(pos) = ctx.input().pointer.hover_pos() { /// // ⚠️ Using `ctx` again here will lead to a dead-lock! /// } /// /// if let Some(pos) = { ctx.input().pointer.hover_pos() } { /// // This is fine! /// } /// /// let pos = ctx.input().pointer.hover_pos(); /// if let Some(pos) = pos { /// // This is fine! /// } /// ``` #[inline] pub fn input(&self) -> RwLockReadGuard<'_, InputState> { RwLockReadGuard::map(self.read(), |c| &c.input) } #[inline] pub fn input_mut(&self) -> RwLockWriteGuard<'_, InputState> { RwLockWriteGuard::map(self.write(), |c| &mut c.input) } /// 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) -> RwLockReadGuard<'_, Fonts> { RwLockReadGuard::map(self.read(), |c| { c.fonts .as_ref() .expect("No fonts available until first call to Context::run()") }) } #[inline] fn fonts_mut(&self) -> RwLockWriteGuard<'_, Option> { RwLockWriteGuard::map(self.write(), |c| &mut c.fonts) } #[inline] pub fn options(&self) -> RwLockWriteGuard<'_, Options> { RwLockWriteGuard::map(self.write(), |c| &mut c.memory.options) } /// Change the options used by the tessellator. #[inline] pub fn tessellation_options(&self) -> RwLockWriteGuard<'_, TessellationOptions> { RwLockWriteGuard::map(self.write(), |c| &mut c.memory.options.tessellation_options) } } impl Context { /// 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. /// /// 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`). pub fn request_repaint(&self) { // request two frames of repaint, just to cover some corner cases (frame delays): let mut ctx = self.write(); ctx.repaint_requests = 2; if let Some(callback) = &ctx.request_repaint_callbacks { (callback)(); } } /// For integrations: this callback will be called when an egui user calls [`Self::request_repaint`]. /// /// This lets you wake up a sleeping UI thread. pub fn set_request_repaint_callback(&self, callback: impl Fn() + Send + Sync + 'static) { let callback = Box::new(callback); self.write().request_repaint_callbacks = Some(callback); } /// Tell `egui` which fonts to use. /// /// The default `egui` fonts only support latin and cyrillic alphabets, /// but you can call this to install additional fonts that support e.g. korean characters. /// /// The new fonts will become active at the start of the next frame. pub fn set_fonts(&self, font_definitions: FontDefinitions) { if let Some(current_fonts) = &*self.fonts_mut() { // NOTE: this comparison is expensive since it checks TTF data for equality if current_fonts.lock().fonts.definitions() == &font_definitions { return; // no change - save us from reloading font textures } } self.memory().new_font_definitions = Some(font_definitions); } /// The [`Style`] used by all subsequent windows, panels etc. pub fn style(&self) -> Arc