Put everything in Context behind the same Mutex (#1050)

* Move all interior mutability from Context to CtxRef and make it a handle
* Rename `CtxRef` to `Context`
* The old `Context` is now `ContextImpl` and is non-pub
* Add benchmark Painter::rect

Co-authored-by: Daniel Keller <dklr433@gmail.com>
This commit is contained in:
Emil Ernerfeldt 2022-01-10 23:13:10 +01:00 committed by GitHub
parent 225d2b506d
commit d5673412dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 550 additions and 495 deletions

View file

@ -11,12 +11,21 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* Added `Ui::add_visible` and `Ui::add_visible_ui`.
### Changed 🔧
* ⚠️ `Context::input` and `Ui::input` now locks a mutex. This can lead to a dead-lock is used in an `if let` binding!
* `if let Some(pos) = ui.input().pointer.latest_pos()` and similar must now be rewritten on two lines, or with added `{}` around the righ-hand-side.
* Search for this problem in your code using the regex `if let .*input`.
* Renamed `CtxRef` to `Context` ([#1050](https://github.com/emilk/egui/pull/1050)).
* `Context` can now be cloned and stored between frames ([#1050](https://github.com/emilk/egui/pull/1050)).
* Renamed `Ui::visible` to `Ui::is_visible`.
* Split `Event::Text` into `Event::Text` and `Event::Paste` ([#1058](https://github.com/emilk/egui/pull/1058)).
### Fixed 🐛
* Context menu now respects the theme ([#1043](https://github.com/emilk/egui/pull/1043))
### Contributors 🙏
* [danielkeller](https://github.com/danielkeller): [#1050](https://github.com/emilk/egui/pull/1050).
## 0.16.1 - 2021-12-31 - Add back `CtxRef::begin_frame,end_frame`
### Added ⭐

View file

@ -365,6 +365,7 @@ Notable contributions by:
* [@AlexApps99](https://github.com/AlexApps99): [`egui_glow`](https://github.com/emilk/egui/pull/685).
* [@mankinskin](https://github.com/mankinskin): [Context menus](https://github.com/emilk/egui/pull/543).
* [@t18b219k](https://github.com/t18b219k): [Port glow painter to web](https://github.com/emilk/egui/pull/868).
* [@danielkeller](https://github.com/danielkeller): [`Context` refactor](https://github.com/emilk/egui/pull/1050).
* And [many more](https://github.com/emilk/egui/graphs/contributors?type=a).
egui is licensed under [MIT](LICENSE-MIT) OR [Apache-2.0](LICENSE-APACHE).

View file

@ -19,7 +19,7 @@ impl epi::App for MyApp {
fn setup(
&mut self,
ctx: &egui::CtxRef,
ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
@ -51,7 +51,7 @@ impl epi::App for MyApp {
ctx.set_fonts(fonts);
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("egui using custom fonts");
ui.text_edit_multiline(&mut self.text);

View file

@ -13,7 +13,7 @@ impl epi::App for MyApp {
"Native file dialogs and drag-and-drop files"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!");
@ -57,7 +57,7 @@ impl epi::App for MyApp {
}
impl MyApp {
fn detect_files_being_dropped(&mut self, ctx: &egui::CtxRef) {
fn detect_files_being_dropped(&mut self, ctx: &egui::Context) {
use egui::*;
// Preview hovering files:

View file

@ -21,7 +21,7 @@ impl epi::App for MyApp {
"My egui App"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
let Self { name, age } = self;
egui::CentralPanel::default().show(ctx, |ui| {

View file

@ -12,7 +12,7 @@ impl epi::App for MyApp {
"Show an image with eframe/egui"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
if self.texture.is_none() {
// Load the image:
let image_data = include_bytes!("rust-logo-256x256.png");

View file

@ -26,7 +26,7 @@
//! "My egui App"
//! }
//!
//! fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
//! fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!");
//! });
@ -129,7 +129,7 @@ pub fn start_web(canvas_id: &str, app: Box<dyn epi::App>) -> Result<(), wasm_bin
/// "My egui App"
/// }
///
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!");
/// });
@ -160,7 +160,7 @@ pub fn run_native(app: Box<dyn epi::App>, native_options: epi::NativeOptions) ->
/// "My egui App"
/// }
///
/// fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
/// fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!");
/// });

View file

@ -191,7 +191,7 @@ impl Persistence {
pub struct EpiIntegration {
frame: epi::Frame,
persistence: crate::epi::Persistence,
pub egui_ctx: egui::CtxRef,
pub egui_ctx: egui::Context,
egui_winit: crate::State,
pub app: Box<dyn epi::App>,
/// When set, it is time to quit
@ -206,7 +206,7 @@ impl EpiIntegration {
persistence: crate::epi::Persistence,
app: Box<dyn epi::App>,
) -> Self {
let egui_ctx = egui::CtxRef::default();
let egui_ctx = egui::Context::default();
*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
@ -250,7 +250,7 @@ impl EpiIntegration {
}
fn warm_up(&mut self, window: &winit::window::Window) {
let saved_memory = self.egui_ctx.memory().clone();
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true);
let (_, tex_alloc_data, _) = self.update(window);
self.frame.lock().output.tex_allocation_data = tex_alloc_data; // handle it next frame

View file

@ -176,7 +176,7 @@ pub(crate) struct Prepared {
impl Area {
pub fn show<R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let prepared = self.begin(ctx);
@ -186,7 +186,7 @@ impl Area {
InnerResponse { inner, response }
}
pub(crate) fn begin(self, ctx: &CtxRef) -> Prepared {
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
let Area {
id,
movable,
@ -234,7 +234,7 @@ impl Area {
}
}
pub fn show_open_close_animation(&self, ctx: &CtxRef, frame: &Frame, is_open: bool) {
pub fn show_open_close_animation(&self, ctx: &Context, frame: &Frame, is_open: bool) {
// must be called first so animation managers know the latest state
let visibility_factor = ctx.animate_bool(self.id.with("close_animation"), is_open);
@ -276,7 +276,7 @@ impl Prepared {
self.drag_bounds
}
pub(crate) fn content_ui(&self, ctx: &CtxRef) -> Ui {
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
let screen_rect = ctx.input().screen_rect();
let bounds = if let Some(bounds) = self.drag_bounds {
@ -317,7 +317,7 @@ impl Prepared {
}
#[allow(clippy::needless_pass_by_value)] // intentional to swallow up `content_ui`.
pub(crate) fn end(self, ctx: &CtxRef, content_ui: Ui) -> Response {
pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
let Prepared {
layer_id,
mut state,
@ -370,8 +370,9 @@ impl Prepared {
}
fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
if let Some(pointer_pos) = ctx.input().pointer.interact_pos() {
ctx.input().pointer.any_pressed() && ctx.layer_id_at(pointer_pos) == Some(layer_id)
if let Some(pointer_pos) = ctx.pointer_interact_pos() {
let any_pressed = ctx.input().pointer.any_pressed();
any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
} else {
false
}

View file

@ -199,7 +199,7 @@ impl SidePanel {
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ui.input().pointer.latest_pos() {
if let Some(pointer) = ui.ctx().latest_pointer_pos() {
let we_are_on_top = ui
.ctx()
.layer_id_at(pointer)
@ -284,7 +284,7 @@ impl SidePanel {
/// Show the panel at the top level.
pub fn show<R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_dyn(ctx, Box::new(add_contents))
@ -293,7 +293,7 @@ impl SidePanel {
/// Show the panel at the top level.
fn show_dyn<'c, R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let layer_id = LayerId::background();
@ -485,7 +485,8 @@ impl TopBottomPanel {
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ui.input().pointer.latest_pos() {
let latest_pos = ui.input().pointer.latest_pos();
if let Some(pointer) = latest_pos {
let we_are_on_top = ui
.ctx()
.layer_id_at(pointer)
@ -570,7 +571,7 @@ impl TopBottomPanel {
/// Show the panel at the top level.
pub fn show<R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_dyn(ctx, Box::new(add_contents))
@ -579,7 +580,7 @@ impl TopBottomPanel {
/// Show the panel at the top level.
fn show_dyn<'c, R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let layer_id = LayerId::background();
@ -670,7 +671,7 @@ impl CentralPanel {
/// Show the panel at the top level.
pub fn show<R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_dyn(ctx, Box::new(add_contents))
@ -679,7 +680,7 @@ impl CentralPanel {
/// Show the panel at the top level.
fn show_dyn<'c, R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let available_rect = ctx.available_rect();

View file

@ -66,7 +66,11 @@ impl MonoState {
/// }
/// # });
/// ```
pub fn show_tooltip<R>(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui) -> R) -> Option<R> {
pub fn show_tooltip<R>(
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
show_tooltip_at_pointer(ctx, id, add_contents)
}
@ -88,7 +92,7 @@ pub fn show_tooltip<R>(ctx: &CtxRef, id: Id, add_contents: impl FnOnce(&mut Ui)
/// # });
/// ```
pub fn show_tooltip_at_pointer<R>(
ctx: &CtxRef,
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {
@ -104,7 +108,7 @@ pub fn show_tooltip_at_pointer<R>(
///
/// If the tooltip does not fit under the area, it tries to place it above it instead.
pub fn show_tooltip_for<R>(
ctx: &CtxRef,
ctx: &Context,
id: Id,
rect: &Rect,
add_contents: impl FnOnce(&mut Ui) -> R,
@ -129,7 +133,7 @@ pub fn show_tooltip_for<R>(
///
/// Returns `None` if the tooltip could not be placed.
pub fn show_tooltip_at<R>(
ctx: &CtxRef,
ctx: &Context,
id: Id,
suggested_position: Option<Pos2>,
add_contents: impl FnOnce(&mut Ui) -> R,
@ -146,7 +150,7 @@ pub fn show_tooltip_at<R>(
}
fn show_tooltip_at_avoid_dyn<'c, R>(
ctx: &CtxRef,
ctx: &Context,
mut id: Id,
suggested_position: Option<Pos2>,
above: bool,
@ -229,7 +233,7 @@ fn show_tooltip_at_avoid_dyn<'c, R>(
/// }
/// # });
/// ```
pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<WidgetText>) -> Option<()> {
pub fn show_tooltip_text(ctx: &Context, id: Id, text: impl Into<WidgetText>) -> Option<()> {
show_tooltip(ctx, id, |ui| {
crate::widgets::Label::new(text).ui(ui);
})
@ -237,7 +241,7 @@ pub fn show_tooltip_text(ctx: &CtxRef, id: Id, text: impl Into<WidgetText>) -> O
/// Show a pop-over window.
fn show_tooltip_area_dyn<'c, R>(
ctx: &CtxRef,
ctx: &Context,
id: Id,
window_pos: Pos2,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,

View file

@ -523,12 +523,11 @@ impl Prepared {
};
let content_response = ui.interact(inner_rect, id.with("area"), sense);
let input = ui.input();
if content_response.dragged() {
for d in 0..2 {
if has_bar[d] {
state.offset[d] -= input.pointer.delta()[d];
state.vel[d] = input.pointer.velocity()[d];
state.offset[d] -= ui.input().pointer.delta()[d];
state.vel[d] = ui.input().pointer.velocity()[d];
state.scroll_stuck_to_end[d] = false;
} else {
state.vel[d] = 0.0;
@ -537,7 +536,7 @@ impl Prepared {
} else {
let stop_speed = 20.0; // Pixels per second.
let friction_coeff = 1000.0; // Pixels per second squared.
let dt = input.unstable_dt;
let dt = ui.input().unstable_dt;
let friction = friction_coeff * dt;
if friction > state.vel.length() || state.vel.length() < stop_speed {

View file

@ -235,7 +235,7 @@ impl<'open> Window<'open> {
#[inline]
pub fn show<R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<Option<R>>> {
self.show_dyn(ctx, Box::new(add_contents))
@ -243,7 +243,7 @@ impl<'open> Window<'open> {
fn show_dyn<'c, R>(
self,
ctx: &CtxRef,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> Option<InnerResponse<Option<R>>> {
let Window {
@ -296,7 +296,7 @@ impl<'open> Window<'open> {
.and_then(|window_interaction| {
// Calculate roughly how much larger the window size is compared to the inner rect
let title_bar_height = if with_title_bar {
title.font_height(ctx.fonts(), &ctx.style()) + title_content_spacing
title.font_height(ctx) + title_content_spacing
} else {
0.0
};
@ -757,7 +757,7 @@ fn show_title_bar(
) -> TitleBar {
let inner_response = ui.horizontal(|ui| {
let height = title
.font_height(ui.fonts(), ui.style())
.font_height(ui.ctx())
.max(ui.spacing().interact_size.y);
ui.set_min_height(height);

View file

@ -1,40 +1,107 @@
// #![warn(missing_docs)]
use std::sync::{
atomic::{AtomicU32, Ordering::SeqCst},
Arc,
};
use crate::{
animation_manager::AnimationManager,
data::output::Output,
frame_state::FrameState,
input_state::*,
layers::GraphicLayers,
menu::ContextMenuSystem,
mutex::{Mutex, MutexGuard},
*,
animation_manager::AnimationManager, data::output::Output, frame_state::FrameState,
input_state::*, layers::GraphicLayers, *,
};
use epaint::{stats::*, text::Fonts, *};
use epaint::{mutex::*, stats::*, text::Fonts, *};
// ----------------------------------------------------------------------------
/// A wrapper around [`Arc`](std::sync::Arc)`<`[`Context`]`>`.
/// This is how you will normally create and access a [`Context`].
#[derive(Default)]
struct ContextImpl {
/// `None` until the start of the first frame.
fonts: Option<Fonts>,
memory: Memory,
animation_manager: AnimationManager,
input: InputState,
/// State that is collected during a frame and then cleared
frame_state: FrameState,
// The output of a frame:
graphics: GraphicLayers,
output: Output,
paint_stats: PaintStats,
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: u32,
}
impl ContextImpl {
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
self.memory.begin_frame(&self.input, &new_raw_input);
let mut input = std::mem::take(&mut self.input);
if let Some(new_pixels_per_point) = self.memory.new_pixels_per_point.take() {
input.pixels_per_point = new_pixels_per_point;
}
self.input = input.begin_frame(new_raw_input);
self.frame_state.begin_frame(&self.input);
self.update_fonts_mut(self.input.pixels_per_point());
// 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, pixels_per_point: f32) {
let new_font_definitions = self.memory.new_font_definitions.take();
let pixels_per_point_changed = match &self.fonts {
None => true,
Some(current_fonts) => {
(current_fonts.pixels_per_point() - pixels_per_point).abs() > 1e-3
}
};
if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed {
self.fonts = Some(Fonts::new(
pixels_per_point,
new_font_definitions.unwrap_or_else(|| {
self.fonts
.as_ref()
.map(|font| font.definitions().clone())
.unwrap_or_default()
}),
));
}
}
}
// ----------------------------------------------------------------------------
/// Your handle to egui.
///
/// Almost all methods are marked `&self`, `Context` has interior mutability (protected by mutexes).
/// This is the first thing you need when working with egui.
/// Contains the [`InputState`], [`Memory`], [`Output`], and more.
///
/// [`CtxRef`] is cheap to clone, and any clones refers to the same mutable data.
/// [`Context`] is cheap to clone, and any clones refers to the same mutable data
/// ([`Context`] uses refcounting internally).
///
/// A [`CtxRef`] is only valid for the duration of a frame, and so you should not store a [`CtxRef`] between frames.
/// A new [`CtxRef`] is created each frame by calling [`Self::run`].
/// All methods are marked `&self`; `Context` has interior mutability (protected by a mutex).
///
///
/// You can store
///
/// # Example:
///
/// ``` no_run
/// # fn handle_output(_: egui::Output) {}
/// # fn paint(_: Vec<egui::ClippedMesh>) {}
/// let mut ctx = egui::CtxRef::default();
/// let mut ctx = egui::Context::default();
///
/// // Game loop:
/// loop {
@ -53,58 +120,46 @@ use epaint::{stats::*, text::Fonts, *};
/// }
/// ```
#[derive(Clone)]
pub struct CtxRef(std::sync::Arc<Context>);
pub struct Context(Arc<RwLock<ContextImpl>>);
impl std::ops::Deref for CtxRef {
type Target = Context;
fn deref(&self) -> &Context {
&*self.0
}
}
impl AsRef<Context> for CtxRef {
fn as_ref(&self) -> &Context {
self.0.as_ref()
}
}
impl std::borrow::Borrow<Context> for CtxRef {
fn borrow(&self) -> &Context {
self.0.borrow()
}
}
impl std::cmp::PartialEq for CtxRef {
fn eq(&self, other: &CtxRef) -> bool {
impl std::cmp::PartialEq for Context {
fn eq(&self, other: &Context) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Default for CtxRef {
impl Default for Context {
fn default() -> Self {
Self(Arc::new(Context {
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: AtomicU32::new(1),
..Context::default()
}))
repaint_requests: 1,
..ContextImpl::default()
})))
}
}
impl CtxRef {
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 [`CtxRef`] will refer to the old [`Context`], which will not get new input.
/// 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`].
///
/// ``` rust
/// // One egui context that you keep reusing:
/// let mut ctx = egui::CtxRef::default();
/// let mut ctx = egui::Context::default();
///
/// // Each frame:
/// let input = egui::RawInput::default();
@ -117,9 +172,9 @@ impl CtxRef {
/// ```
#[must_use]
pub fn run(
&mut self,
&self,
new_input: RawInput,
run_ui: impl FnOnce(&CtxRef),
run_ui: impl FnOnce(&Context),
) -> (Output, Vec<ClippedShape>) {
self.begin_frame(new_input);
run_ui(self);
@ -130,7 +185,7 @@ impl CtxRef {
///
/// ``` rust
/// // One egui context that you keep reusing:
/// let mut ctx = egui::CtxRef::default();
/// let mut ctx = egui::Context::default();
///
/// // Each frame:
/// let input = egui::RawInput::default();
@ -143,10 +198,8 @@ impl CtxRef {
/// let (output, shapes) = ctx.end_frame();
/// // handle output, paint shapes
/// ```
pub fn begin_frame(&mut self, new_input: RawInput) {
let mut self_: Context = (*self.0).clone();
self_.begin_frame_mut(new_input);
*self = Self(Arc::new(self_));
pub fn begin_frame(&self, new_input: RawInput) {
self.write().begin_frame_mut(new_input);
}
// ---------------------------------------------------------------------
@ -167,7 +220,7 @@ impl CtxRef {
let show_error = |pos: Pos2, text: String| {
let painter = self.debug_painter();
let rect = painter.error(pos, text);
if let Some(pointer_pos) = self.input.pointer.hover_pos() {
if let Some(pointer_pos) = self.pointer_hover_pos() {
if rect.contains(pointer_pos) {
painter.error(
rect.left_bottom() + vec2(2.0, 4.0),
@ -244,14 +297,19 @@ impl CtxRef {
changed: false, // must be set by the widget itself
};
let mut memory = self.memory();
if !enabled || !sense.focusable || !layer_id.allow_interaction() {
// Not interested or allowed input:
memory.surrender_focus(id);
self.memory().surrender_focus(id);
return response;
}
self.register_interaction_id(id, rect);
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;
@ -262,14 +320,12 @@ impl CtxRef {
if sense.click
&& memory.has_focus(response.id)
&& (self.input().key_pressed(Key::Space) || self.input().key_pressed(Key::Enter))
&& (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;
}
self.register_interaction_id(id, rect);
if sense.click || sense.drag {
memory.interaction.click_interest |= hovered && sense.click;
memory.interaction.drag_interest |= hovered && sense.drag;
@ -278,7 +334,7 @@ impl CtxRef {
response.is_pointer_button_down_on =
memory.interaction.click_id == Some(id) || response.dragged;
for pointer_event in &self.input.pointer.pointer_events {
for pointer_event in &input.pointer.pointer_events {
match pointer_event {
PointerEvent::Moved(_) => {}
PointerEvent::Pressed(_) => {
@ -325,14 +381,14 @@ impl CtxRef {
}
if response.is_pointer_button_down_on {
response.interact_pointer_pos = self.input().pointer.interact_pos();
response.interact_pointer_pos = input.pointer.interact_pos();
}
if self.input.pointer.any_down() {
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) && response.clicked_elsewhere() {
if memory.has_focus(response.id) && clicked_elsewhere {
memory.surrender_focus(id);
}
@ -346,131 +402,99 @@ impl CtxRef {
/// Get a full-screen painter for a new or existing layer
pub fn layer_painter(&self, layer_id: LayerId) -> Painter {
Painter::new(self.clone(), layer_id, self.input.screen_rect())
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())
}
}
// ----------------------------------------------------------------------------
/// Your handle to egui.
///
/// This is the first thing you need when working with egui.
/// Use [`CtxRef`] to create and refer to a [`Context`].
///
/// Contains the [`InputState`], [`Memory`], [`Output`], and more.
///
/// Almost all methods are marked `&self`, [`Context`] has interior mutability (protected by mutexes).
/// Multi-threaded access to a [`Context`] is behind the feature flag `multi_threaded`.
/// Normally you'd always do all ui work on one thread, or perhaps use multiple contexts,
/// but if you really want to access the same [`Context`] from multiple threads, it *SHOULD* be fine,
/// but you are likely the first person to try it.
#[derive(Default)]
pub struct Context {
// We clone the Context each frame so we can set a new `input`.
// This is so we can avoid a mutex lock to access the `InputState`.
// This means everything else needs to be behind an Arc.
// We can probably come up with a nicer design.
//
/// `None` until the start of the first frame.
fonts: Option<Arc<Fonts>>,
memory: Arc<Mutex<Memory>>,
animation_manager: Arc<Mutex<AnimationManager>>,
context_menu_system: Arc<Mutex<ContextMenuSystem>>,
input: InputState,
/// State that is collected during a frame and then cleared
frame_state: Arc<Mutex<FrameState>>,
// The output of a frame:
graphics: Arc<Mutex<GraphicLayers>>,
output: Arc<Mutex<Output>>,
paint_stats: Arc<Mutex<PaintStats>>,
/// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: AtomicU32,
}
impl Clone for Context {
fn clone(&self) -> Self {
Context {
fonts: self.fonts.clone(),
memory: self.memory.clone(),
animation_manager: self.animation_manager.clone(),
input: self.input.clone(),
frame_state: self.frame_state.clone(),
graphics: self.graphics.clone(),
output: self.output.clone(),
paint_stats: self.paint_stats.clone(),
repaint_requests: self.repaint_requests.load(SeqCst).into(),
context_menu_system: self.context_menu_system.clone(),
}
}
}
impl Context {
/// How much space is still available after panels has been added.
/// This is the "background" area, what egui doesn't cover with panels (but may cover with windows).
/// This is also the area to which windows are constrained.
pub fn available_rect(&self) -> Rect {
self.frame_state.lock().available_rect()
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.
pub fn memory(&self) -> MutexGuard<'_, Memory> {
self.memory.lock()
pub fn memory(&self) -> RwLockWriteGuard<'_, Memory> {
RwLockWriteGuard::map(self.write(), |c| &mut c.memory)
}
pub(crate) fn context_menu_system(&self) -> MutexGuard<'_, ContextMenuSystem> {
self.context_menu_system.lock()
}
pub(crate) fn graphics(&self) -> MutexGuard<'_, GraphicLayers> {
self.graphics.lock()
pub(crate) fn graphics(&self) -> RwLockWriteGuard<'_, GraphicLayers> {
RwLockWriteGuard::map(self.write(), |c| &mut c.graphics)
}
/// What egui outputs each frame.
pub fn output(&self) -> MutexGuard<'_, Output> {
self.output.lock()
pub fn output(&self) -> RwLockWriteGuard<'_, Output> {
RwLockWriteGuard::map(self.write(), |c| &mut c.output)
}
pub(crate) fn frame_state(&self) -> MutexGuard<'_, FrameState> {
self.frame_state.lock()
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(always)]
pub fn input(&self) -> RwLockReadGuard<'_, InputState> {
RwLockReadGuard::map(self.read(), |c| &c.input)
}
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.
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()")
})
}
fn fonts_mut(&self) -> RwLockWriteGuard<'_, Option<Fonts>> {
RwLockWriteGuard::map(self.write(), |c| &mut c.fonts)
}
}
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.
pub fn request_repaint(&self) {
// request two frames of repaint, just to cover some corner cases (frame delays):
let times_to_repaint = 2;
self.repaint_requests.store(times_to_repaint, SeqCst);
}
#[inline(always)]
pub fn input(&self) -> &InputState {
&self.input
}
/// Not valid until first call to [`CtxRef::run()`].
/// That's because since we don't know the proper `pixels_per_point` until then.
pub fn fonts(&self) -> &Fonts {
&*self
.fonts
.as_ref()
.expect("No fonts available until first call to CtxRef::run()")
self.write().repaint_requests = 2;
}
/// The egui font image, containing font characters etc.
///
/// Not valid until first call to [`CtxRef::run()`].
/// Not valid until first call to [`Context::run()`].
/// That's because since we don't know the proper `pixels_per_point` until then.
pub fn font_image(&self) -> Arc<epaint::FontImage> {
self.fonts().font_image()
@ -483,7 +507,7 @@ impl Context {
///
/// 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 {
if let Some(current_fonts) = &*self.fonts_mut() {
// NOTE: this comparison is expensive since it checks TTF data for equality
if current_fonts.definitions() == &font_definitions {
return; // no change - save us from reloading font textures
@ -504,7 +528,7 @@ impl Context {
///
/// Example:
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// # let mut ctx = egui::Context::default();
/// let mut style: egui::Style = (*ctx.style()).clone();
/// style.spacing.item_spacing = egui::vec2(10.0, 20.0);
/// ctx.set_style(style);
@ -519,7 +543,7 @@ impl Context {
///
/// Example:
/// ```
/// # let mut ctx = egui::CtxRef::default();
/// # let mut ctx = egui::Context::default();
/// ctx.set_visuals(egui::Visuals::light()); // Switch to light mode
/// ```
pub fn set_visuals(&self, visuals: crate::Visuals) {
@ -529,7 +553,7 @@ impl Context {
/// The number of physical pixels for each logical point.
#[inline(always)]
pub fn pixels_per_point(&self) -> f32 {
self.input.pixels_per_point()
self.input().pixels_per_point()
}
/// Set the number of physical pixels for each logical point.
@ -604,75 +628,30 @@ impl Context {
Rect::from_min_size(pos, window.size())
}
}
// ---------------------------------------------------------------------
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
self.memory().begin_frame(&self.input, &new_raw_input);
let mut input = std::mem::take(&mut self.input);
if let Some(new_pixels_per_point) = self.memory().new_pixels_per_point.take() {
input.pixels_per_point = new_pixels_per_point;
}
self.input = input.begin_frame(new_raw_input);
self.frame_state.lock().begin_frame(&self.input);
self.update_fonts(self.input.pixels_per_point());
// 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 self, pixels_per_point: f32) {
let new_font_definitions = self.memory().new_font_definitions.take();
let pixels_per_point_changed = match &self.fonts {
None => true,
Some(current_fonts) => {
(current_fonts.pixels_per_point() - pixels_per_point).abs() > 1e-3
}
};
if self.fonts.is_none() || new_font_definitions.is_some() || pixels_per_point_changed {
self.fonts = Some(Arc::new(Fonts::new(
pixels_per_point,
new_font_definitions.unwrap_or_else(|| {
self.fonts
.as_ref()
.map(|font| font.definitions().clone())
.unwrap_or_default()
}),
)));
}
}
impl Context {
/// Call at the end of each frame.
/// Returns what has happened this frame [`crate::Output`] as well as what you need to paint.
/// You can transform the returned shapes into triangles with a call to [`Context::tessellate`].
#[must_use]
pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
if self.input.wants_repaint() {
if self.input().wants_repaint() {
self.request_repaint();
}
self.memory()
.end_frame(&self.input, &self.frame_state().used_ids);
{
let ctx_impl = &mut *self.write();
ctx_impl
.memory
.end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids);
}
self.fonts().end_frame();
let mut output: Output = std::mem::take(&mut self.output());
if self.repaint_requests.load(SeqCst) > 0 {
self.repaint_requests.fetch_sub(1, SeqCst);
if self.read().repaint_requests > 0 {
self.write().repaint_requests -= 1;
output.needs_repaint = true;
}
@ -681,8 +660,11 @@ impl Context {
}
fn drain_paint_lists(&self) -> Vec<ClippedShape> {
let memory = self.memory();
self.graphics().drain(memory.areas.order()).collect()
let ctx_impl = &mut *self.write();
ctx_impl
.graphics
.drain(ctx_impl.memory.areas.order())
.collect()
}
/// Tessellate the given shapes into triangle meshes.
@ -700,7 +682,7 @@ impl Context {
tessellation_options,
self.fonts().font_image().size(),
);
*self.paint_stats.lock() = paint_stats.with_clipped_meshes(&clipped_meshes);
self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes);
clipped_meshes
}
@ -725,7 +707,8 @@ impl Context {
/// Is the pointer (mouse/touch) over any egui area?
pub fn is_pointer_over_area(&self) -> bool {
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
let pointer_pos = self.input().pointer.interact_pos();
if let Some(pointer_pos) = pointer_pos {
if let Some(layer) = self.layer_id_at(pointer_pos) {
if layer.order == Order::Background {
!self.frame_state().unused_rect.contains(pointer_pos)
@ -759,14 +742,45 @@ impl Context {
pub fn wants_keyboard_input(&self) -> bool {
self.memory().interaction.focus.focused().is_some()
}
}
// ---------------------------------------------------------------------
// Ergonomic methods to forward some calls often used in 'if let' without holding the borrow
impl Context {
/// Latest reported pointer position.
/// When tapping a touch screen, this will be `None`.
#[inline(always)]
pub(crate) fn latest_pointer_pos(&self) -> Option<Pos2> {
self.input().pointer.latest_pos()
}
/// If it is a good idea to show a tooltip, where is pointer?
#[inline(always)]
pub fn pointer_hover_pos(&self) -> Option<Pos2> {
self.input().pointer.hover_pos()
}
/// If you detect a click or drag and wants to know where it happened, use this.
///
/// Latest position of the mouse, but ignoring any [`Event::PointerGone`]
/// if there were interactions this frame.
/// When tapping a touch screen, this will be the location of the touch.
#[inline(always)]
pub fn pointer_interact_pos(&self) -> Option<Pos2> {
self.input().pointer.interact_pos()
}
/// Calls [`InputState::multi_touch`].
pub fn multi_touch(&self) -> Option<MultiTouchInfo> {
self.input().multi_touch()
}
}
impl Context {
/// Move all the graphics at the given layer.
/// Can be used to implement drag-and-drop (see relevant demo).
pub fn translate_layer(&self, layer_id: LayerId, delta: Vec2) {
if delta != Vec2::ZERO {
self.graphics().list(layer_id).lock().translate(delta);
self.graphics().list(layer_id).translate(delta);
}
}
@ -777,7 +791,8 @@ impl Context {
}
pub(crate) fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
let pointer_pos = self.input().pointer.interact_pos();
if let Some(pointer_pos) = pointer_pos {
rect.contains(pointer_pos) && self.layer_id_at(pointer_pos) == Some(layer_id)
} else {
false
@ -817,10 +832,12 @@ impl Context {
/// Like [`Self::animate_bool`] but allows you to control the animation time.
pub fn animate_bool_with_time(&self, id: Id, value: bool, animation_time: f32) -> f32 {
let animated_value =
self.animation_manager
.lock()
.animate_bool(&self.input, animation_time, id, value);
let animated_value = {
let ctx_impl = &mut *self.write();
ctx_impl
.animation_manager
.animate_bool(&ctx_impl.input, animation_time, id, value)
};
let animation_in_progress = 0.0 < animated_value && animated_value < 1.0;
if animation_in_progress {
self.request_repaint();
@ -830,7 +847,7 @@ impl Context {
/// Clear memory of any animations.
pub fn clear_animations(&self) {
*self.animation_manager.lock() = Default::default();
self.write().animation_manager = Default::default();
}
}
@ -849,7 +866,8 @@ impl Context {
.show(ui, |ui| {
let mut font_definitions = self.fonts().definitions().clone();
font_definitions.ui(ui);
self.fonts().font_image().ui(ui);
let font_image = self.fonts().font_image();
font_image.ui(ui);
self.set_fonts(font_definitions);
});
@ -891,16 +909,12 @@ impl Context {
.on_hover_text("Is egui currently listening for text input?");
let pointer_pos = self
.input()
.pointer
.hover_pos()
.pointer_hover_pos()
.map_or_else(String::new, |pos| format!("{:?}", pos));
ui.label(format!("Pointer pos: {}", pointer_pos));
let top_layer = self
.input()
.pointer
.hover_pos()
.pointer_hover_pos()
.and_then(|pos| self.layer_id_at(pos))
.map_or_else(String::new, |layer| layer.short_debug_format());
ui.label(format!("Top layer under mouse: {}", top_layer));
@ -916,12 +930,16 @@ impl Context {
CollapsingHeader::new("📥 Input")
.default_open(false)
.show(ui, |ui| ui.input().clone().ui(ui));
.show(ui, |ui| {
let input = ui.input().clone();
input.ui(ui);
});
CollapsingHeader::new("📊 Paint stats")
.default_open(true)
.show(ui, |ui| {
self.paint_stats.lock().ui(ui);
let paint_stats = self.write().paint_stats;
paint_stats.ui(ui);
});
}

View file

@ -135,7 +135,7 @@ pub struct DroppedFile {
/// Set by the `egui_web` backend.
pub last_modified: Option<std::time::SystemTime>,
/// Set by the `egui_web` backend.
pub bytes: Option<std::sync::Arc<[u8]>>,
pub bytes: Option<epaint::mutex::Arc<[u8]>>,
}
/// An input event generated by the integration.

View file

@ -72,7 +72,7 @@ impl FrameState {
pub(crate) fn available_rect(&self) -> Rect {
crate::egui_assert!(
self.available_rect.is_finite(),
"Called `available_rect()` before `CtxRef::run()`"
"Called `available_rect()` before `Context::run()`"
);
self.available_rect
}

View file

@ -45,8 +45,8 @@ impl State {
// ----------------------------------------------------------------------------
pub(crate) struct GridLayout {
ctx: CtxRef,
style: std::sync::Arc<Style>,
ctx: Context,
style: epaint::mutex::Arc<Style>,
id: Id,
/// State previous frame (if any).

View file

@ -261,7 +261,8 @@ impl InputState {
/// # egui::__run_test_ui(|ui| {
/// let mut zoom = 1.0; // no zoom
/// let mut rotation = 0.0; // no rotation
/// if let Some(multi_touch) = ui.input().multi_touch() {
/// let multi_touch = ui.input().multi_touch();
/// if let Some(multi_touch) = multi_touch {
/// zoom *= multi_touch.zoom_delta;
/// rotation += multi_touch.rotation_delta;
/// }

View file

@ -7,6 +7,7 @@ use crate::{
};
/// All you probably need to know about a multi-touch gesture.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct MultiTouchInfo {
/// Point in time when the gesture started.
pub start_time: f64,

View file

@ -32,7 +32,7 @@ impl Widget for &epaint::FontImage {
response
.on_hover_cursor(CursorIcon::ZoomIn)
.on_hover_ui_at_pointer(|ui| {
if let Some(pos) = ui.input().pointer.latest_pos() {
if let Some(pos) = ui.ctx().latest_pointer_pos() {
let (_id, zoom_rect) = ui.allocate_space(vec2(128.0, 128.0));
let u = remap_clamp(pos.x, rect.x_range(), 0.0..=tex_w);
let v = remap_clamp(pos.y, rect.y_range(), 0.0..=tex_h);

View file

@ -2,9 +2,7 @@
//! are sometimes painted behind or in front of other things.
use crate::{Id, *};
use epaint::mutex::Mutex;
use epaint::{ClippedShape, Shape};
use std::sync::Arc;
/// Different layer categories
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
@ -153,10 +151,10 @@ impl PaintList {
}
#[derive(Clone, Default)]
pub(crate) struct GraphicLayers([IdMap<Arc<Mutex<PaintList>>>; Order::COUNT]);
pub(crate) struct GraphicLayers([IdMap<PaintList>; Order::COUNT]);
impl GraphicLayers {
pub fn list(&mut self, layer_id: LayerId) -> &Arc<Mutex<PaintList>> {
pub fn list(&mut self, layer_id: LayerId) -> &mut PaintList {
self.0[layer_id.order as usize]
.entry(layer_id.id)
.or_default()
@ -171,20 +169,20 @@ impl GraphicLayers {
// If a layer is empty at the start of the frame
// then nobody has added to it, and it is old and defunct.
// Free it to save memory:
order_map.retain(|_, list| !list.lock().is_empty());
order_map.retain(|_, list| !list.is_empty());
// First do the layers part of area_order:
for layer_id in area_order {
if layer_id.order == order {
if let Some(list) = order_map.get_mut(&layer_id.id) {
all_shapes.append(&mut list.lock().0);
all_shapes.append(&mut list.0);
}
}
}
// Also draw areas that are missing in `area_order`:
for shapes in order_map.values_mut() {
all_shapes.append(&mut shapes.lock().0);
all_shapes.append(&mut shapes.0);
}
}

View file

@ -8,7 +8,7 @@
//! To quickly get started with egui, you can take a look at [`eframe_template`](https://github.com/emilk/eframe_template)
//! which uses [`eframe`](https://docs.rs/eframe).
//!
//! To create a GUI using egui you first need a [`CtxRef`] (by convention referred to by `ctx`).
//! To create a GUI using egui you first need a [`Context`] (by convention referred to by `ctx`).
//! Then you add a [`Window`] or a [`SidePanel`] to get a [`Ui`], which is what you'll be using to add all the buttons and labels that you need.
//!
//!
@ -113,7 +113,7 @@
//! # fn handle_output(_: egui::Output) {}
//! # fn paint(_: Vec<egui::ClippedMesh>) {}
//! # fn gather_input() -> egui::RawInput { egui::RawInput::default() }
//! let mut ctx = egui::CtxRef::default();
//! let mut ctx = egui::Context::default();
//!
//! // Game loop:
//! loop {
@ -398,7 +398,7 @@ pub mod text {
pub use {
containers::*,
context::{Context, CtxRef},
context::Context,
data::{
input::*,
output::{self, CursorIcon, Output, WidgetInfo},
@ -574,8 +574,8 @@ pub enum WidgetType {
// ----------------------------------------------------------------------------
/// For use in tests; especially doctests.
pub fn __run_test_ctx(mut run_ui: impl FnMut(&CtxRef)) {
let mut ctx = CtxRef::default();
pub fn __run_test_ctx(mut run_ui: impl FnMut(&Context)) {
let ctx = Context::default();
let _ = ctx.run(Default::default(), |ctx| {
run_ui(ctx);
});
@ -583,7 +583,7 @@ pub fn __run_test_ctx(mut run_ui: impl FnMut(&CtxRef)) {
/// For use in tests; especially doctests.
pub fn __run_test_ui(mut add_contents: impl FnMut(&mut Ui)) {
let mut ctx = CtxRef::default();
let ctx = Context::default();
let _ = ctx.run(Default::default(), |ctx| {
crate::CentralPanel::default().show(ctx, |ui| {
add_contents(ui);

View file

@ -45,7 +45,7 @@ pub struct Memory {
/// }
/// type CharCountCache<'a> = FrameCache<usize, CharCounter>;
///
/// # let mut ctx = egui::CtxRef::default();
/// # let mut ctx = egui::Context::default();
/// let mut memory = ctx.memory();
/// let cache = memory.caches.cache::<CharCountCache<'_>>();
/// assert_eq!(cache.get("hello"), 5);
@ -91,7 +91,7 @@ pub struct Memory {
pub struct Options {
/// The default style for new `Ui`:s.
#[cfg_attr(feature = "serde", serde(skip))]
pub(crate) style: std::sync::Arc<Style>,
pub(crate) style: epaint::mutex::Arc<Style>,
/// Controls the tessellator.
pub tessellation_options: epaint::TessellationOptions,

View file

@ -16,12 +16,11 @@
//! ```
use super::{
style::WidgetVisuals, Align, CtxRef, Id, InnerResponse, PointerState, Pos2, Rect, Response,
style::WidgetVisuals, Align, Context, Id, InnerResponse, PointerState, Pos2, Rect, Response,
Sense, TextStyle, Ui, Vec2,
};
use crate::{widgets::*, *};
use epaint::{mutex::RwLock, Stroke};
use std::sync::Arc;
use epaint::{mutex::Arc, mutex::RwLock, Stroke};
/// What is saved between frames.
#[derive(Clone, Default)]
@ -116,7 +115,7 @@ pub(crate) fn submenu_button<R>(
/// wrapper for the contents of every menu.
pub(crate) fn menu_ui<'c, R>(
ctx: &CtxRef,
ctx: &Context,
menu_id: impl std::hash::Hash,
menu_state_arc: &Arc<RwLock<MenuState>>,
add_contents: impl FnOnce(&mut Ui) -> R + 'c,
@ -187,33 +186,19 @@ fn stationary_menu_impl<'c, R>(
InnerResponse::new(inner.map(|r| r.inner), button_response)
}
/// Stores the state for the context menu.
#[derive(Default)]
pub(crate) struct ContextMenuSystem {
root: MenuRootManager,
}
impl ContextMenuSystem {
/// Show a menu at pointer if right-clicked response.
/// Should be called from [`Context`] on a [`Response`]
pub fn context_menu(
&mut self,
response: &Response,
add_contents: impl FnOnce(&mut Ui),
) -> Option<InnerResponse<()>> {
MenuRoot::context_click_interaction(response, &mut self.root, response.id);
self.root.show(response, add_contents)
}
}
impl std::ops::Deref for ContextMenuSystem {
type Target = MenuRootManager;
fn deref(&self) -> &Self::Target {
&self.root
}
}
impl std::ops::DerefMut for ContextMenuSystem {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.root
}
/// Response to secondary clicks (right-clicks) by showing the given menu.
pub(crate) fn context_menu(
response: &Response,
add_contents: impl FnOnce(&mut Ui),
) -> Option<InnerResponse<()>> {
let menu_id = Id::new("__egui::context_menu");
let mut bar_state = BarState::load(&response.ctx, menu_id);
MenuRoot::context_click_interaction(response, &mut bar_state, response.id);
let inner_response = bar_state.show(response, add_contents);
bar_state.store(&response.ctx, menu_id);
inner_response
}
/// Stores the state for the context menu.
@ -520,7 +505,7 @@ impl MenuState {
self.response = MenuResponse::Close;
}
pub fn show<R>(
ctx: &CtxRef,
ctx: &Context,
menu_state: &Arc<RwLock<Self>>,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
@ -529,7 +514,7 @@ impl MenuState {
}
fn show_submenu<R>(
&mut self,
ctx: &CtxRef,
ctx: &Context,
id: Id,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<R> {

View file

@ -1,10 +1,10 @@
use crate::{
emath::{Align2, Pos2, Rect, Vec2},
layers::{LayerId, PaintList, ShapeIdx},
Color32, CtxRef,
Color32, Context,
};
use epaint::{
mutex::Mutex,
mutex::{Arc, RwLockReadGuard, RwLockWriteGuard},
text::{Fonts, Galley, TextStyle},
CircleShape, RectShape, Shape, Stroke, TextShape,
};
@ -15,13 +15,11 @@ use epaint::{
#[derive(Clone)]
pub struct Painter {
/// Source of fonts and destination of shapes
ctx: CtxRef,
ctx: Context,
/// Where we paint
layer_id: LayerId,
paint_list: std::sync::Arc<Mutex<PaintList>>,
/// Everything painted in this `Painter` will be clipped against this.
/// This means nothing outside of this rectangle will be visible on screen.
clip_rect: Rect,
@ -32,12 +30,10 @@ pub struct Painter {
}
impl Painter {
pub fn new(ctx: CtxRef, layer_id: LayerId, clip_rect: Rect) -> Self {
let paint_list = ctx.graphics().list(layer_id).clone();
pub fn new(ctx: Context, layer_id: LayerId, clip_rect: Rect) -> Self {
Self {
ctx,
layer_id,
paint_list,
clip_rect,
fade_to_color: None,
}
@ -45,10 +41,8 @@ impl Painter {
#[must_use]
pub fn with_layer_id(self, layer_id: LayerId) -> Self {
let paint_list = self.ctx.graphics().list(layer_id).clone();
Self {
ctx: self.ctx,
paint_list,
layer_id,
clip_rect: self.clip_rect,
fade_to_color: None,
@ -58,7 +52,6 @@ impl Painter {
/// redirect
pub fn set_layer_id(&mut self, layer_id: LayerId) {
self.layer_id = layer_id;
self.paint_list = self.ctx.graphics().list(self.layer_id).clone();
}
/// If set, colors will be modified to look like this
@ -83,7 +76,6 @@ impl Painter {
Self {
ctx: self.ctx.clone(),
layer_id: self.layer_id,
paint_list: self.paint_list.clone(),
clip_rect: rect.intersect(self.clip_rect),
fade_to_color: self.fade_to_color,
}
@ -92,15 +84,15 @@ impl Painter {
/// ## Accessors etc
impl Painter {
/// Get a reference to the parent [`CtxRef`].
/// Get a reference to the parent [`Context`].
#[inline(always)]
pub fn ctx(&self) -> &CtxRef {
pub fn ctx(&self) -> &Context {
&self.ctx
}
/// Available fonts.
#[inline(always)]
pub fn fonts(&self) -> &Fonts {
pub fn fonts(&self) -> RwLockReadGuard<'_, Fonts> {
self.ctx.fonts()
}
@ -145,6 +137,10 @@ impl Painter {
/// ## Low level
impl Painter {
fn paint_list(&self) -> RwLockWriteGuard<'_, PaintList> {
RwLockWriteGuard::map(self.ctx.graphics(), |g| g.list(self.layer_id))
}
fn transform_shape(&self, shape: &mut Shape) {
if let Some(fade_to_color) = self.fade_to_color {
tint_shape_towards(shape, fade_to_color);
@ -156,11 +152,11 @@ impl Painter {
/// NOTE: all coordinates are screen coordinates!
pub fn add(&self, shape: impl Into<Shape>) -> ShapeIdx {
if self.fade_to_color == Some(Color32::TRANSPARENT) {
self.paint_list.lock().add(self.clip_rect, Shape::Noop)
self.paint_list().add(self.clip_rect, Shape::Noop)
} else {
let mut shape = shape.into();
self.transform_shape(&mut shape);
self.paint_list.lock().add(self.clip_rect, shape)
self.paint_list().add(self.clip_rect, shape)
}
}
@ -178,7 +174,7 @@ impl Painter {
}
}
self.paint_list.lock().extend(self.clip_rect, shapes);
self.paint_list().extend(self.clip_rect, shapes);
}
}
@ -189,7 +185,7 @@ impl Painter {
}
let mut shape = shape.into();
self.transform_shape(&mut shape);
self.paint_list.lock().set(idx, self.clip_rect, shape);
self.paint_list().set(idx, self.clip_rect, shape);
}
}
@ -357,7 +353,7 @@ impl Painter {
text_style: TextStyle,
color: crate::Color32,
wrap_width: f32,
) -> std::sync::Arc<Galley> {
) -> Arc<Galley> {
self.fonts().layout(text, text_style, color, wrap_width)
}
@ -370,7 +366,7 @@ impl Painter {
text: String,
text_style: TextStyle,
color: crate::Color32,
) -> std::sync::Arc<Galley> {
) -> Arc<Galley> {
self.fonts().layout(text, text_style, color, f32::INFINITY)
}
@ -380,7 +376,7 @@ impl Painter {
///
/// If you want to change the color of the text, use [`Self::galley_with_color`].
#[inline(always)]
pub fn galley(&self, pos: Pos2, galley: std::sync::Arc<Galley>) {
pub fn galley(&self, pos: Pos2, galley: Arc<Galley>) {
if !galley.is_empty() {
self.add(Shape::galley(pos, galley));
}
@ -392,12 +388,7 @@ impl Painter {
///
/// The text color in the [`Galley`] will be replaced with the given color.
#[inline(always)]
pub fn galley_with_color(
&self,
pos: Pos2,
galley: std::sync::Arc<Galley>,
text_color: Color32,
) {
pub fn galley_with_color(&self, pos: Pos2, galley: Arc<Galley>, text_color: Color32) {
if !galley.is_empty() {
self.add(TextShape {
override_text_color: Some(text_color),

View file

@ -1,6 +1,7 @@
use crate::{
emath::{lerp, Align, Pos2, Rect, Vec2},
CtxRef, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText, NUM_POINTER_BUTTONS,
menu, Context, CursorIcon, Id, LayerId, PointerButton, Sense, Ui, WidgetText,
NUM_POINTER_BUTTONS,
};
// ----------------------------------------------------------------------------
@ -16,7 +17,7 @@ use crate::{
pub struct Response {
// CONTEXT:
/// Used for optionally showing a tooltip and checking for more interactions.
pub ctx: CtxRef,
pub ctx: Context,
// IN:
/// Which layer the widget is part of.
@ -492,9 +493,7 @@ impl Response {
///
/// See also: [`Ui::menu_button`] and [`Ui::close_menu`].
pub fn context_menu(self, add_contents: impl FnOnce(&mut Ui)) -> Self {
self.ctx
.context_menu_system()
.context_menu(&self, add_contents);
menu::context_menu(&self, add_contents);
self
}
}

View file

@ -1,12 +1,11 @@
// #![warn(missing_docs)]
use epaint::mutex::RwLock;
use epaint::mutex::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::hash::Hash;
use std::sync::Arc;
use crate::{
color::*, containers::*, epaint::text::Fonts, layout::*, menu::MenuState, mutex::MutexGuard,
placer::Placer, widgets::*, *,
color::*, containers::*, epaint::text::Fonts, layout::*, menu::MenuState, placer::Placer,
widgets::*, *,
};
// ----------------------------------------------------------------------------
@ -49,7 +48,7 @@ pub struct Ui {
/// The `Style` (visuals, spacing, etc) of this ui.
/// Commonly many `Ui`:s share the same `Style`.
/// The `Ui` implements copy-on-write for this.
style: std::sync::Arc<Style>,
style: Arc<Style>,
/// Handles the `Ui` size and the placement of new widgets.
placer: Placer,
@ -70,7 +69,7 @@ impl Ui {
///
/// Normally you would not use this directly, but instead use
/// [`SidePanel`], [`TopBottomPanel`], [`CentralPanel`], [`Window`] or [`Area`].
pub fn new(ctx: CtxRef, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
pub fn new(ctx: Context, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
let style = ctx.style();
Ui {
id,
@ -122,7 +121,7 @@ impl Ui {
///
/// Note that this may be a different [`Style`] than that of [`Context::style`].
#[inline]
pub fn style(&self) -> &std::sync::Arc<Style> {
pub fn style(&self) -> &Arc<Style> {
&self.style
}
@ -138,13 +137,13 @@ impl Ui {
/// # });
/// ```
pub fn style_mut(&mut self) -> &mut Style {
std::sync::Arc::make_mut(&mut self.style) // clone-on-write
Arc::make_mut(&mut self.style) // clone-on-write
}
/// Changes apply to this `Ui` and its subsequent children.
///
/// To set the visuals of all `Ui`:s, use [`Context::set_visuals`].
pub fn set_style(&mut self, style: impl Into<std::sync::Arc<Style>>) {
pub fn set_style(&mut self, style: impl Into<Arc<Style>>) {
self.style = style.into();
}
@ -195,9 +194,9 @@ impl Ui {
&mut self.style_mut().visuals
}
/// Get a reference to the parent [`CtxRef`].
/// Get a reference to the parent [`Context`].
#[inline]
pub fn ctx(&self) -> &CtxRef {
pub fn ctx(&self) -> &Context {
self.painter.ctx()
}
@ -313,31 +312,50 @@ impl Ui {
self.painter().layer_id()
}
/// The `Input` of the `Context` associated with the `Ui`.
/// The [`InputState`] of the [`Context`] associated with this [`Ui`].
/// Equivalent to `.ctx().input()`.
///
/// Note that this locks the [`Context`], so be careful with if-let bindings:
///
/// ```
/// # egui::__run_test_ui(|ui| {
/// if let Some(pos) = { ui.input().pointer.hover_pos() } {
/// // This is fine!
/// }
///
/// let pos = ui.input().pointer.hover_pos();
/// if let Some(pos) = pos {
/// // This is also fine!
/// }
///
/// if let Some(pos) = ui.input().pointer.hover_pos() {
/// // ⚠️ Using `ui` again here will lead to a dead-lock!
/// }
/// # });
/// ```
#[inline]
pub fn input(&self) -> &InputState {
pub fn input(&self) -> RwLockReadGuard<'_, InputState> {
self.ctx().input()
}
/// The `Memory` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().memory()`.
#[inline]
pub fn memory(&self) -> MutexGuard<'_, Memory> {
pub fn memory(&self) -> RwLockWriteGuard<'_, Memory> {
self.ctx().memory()
}
/// The `Output` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().output()`.
#[inline]
pub fn output(&self) -> MutexGuard<'_, Output> {
pub fn output(&self) -> RwLockWriteGuard<'_, Output> {
self.ctx().output()
}
/// The `Fonts` of the `Context` associated with the `Ui`.
/// Equivalent to `.ctx().fonts()`.
#[inline]
pub fn fonts(&self) -> &Fonts {
pub fn fonts(&self) -> RwLockReadGuard<'_, Fonts> {
self.ctx().fonts()
}

View file

@ -3,8 +3,8 @@
// For non-serializable types, these simply return `None`.
// This will also allow users to pick their own serialization format per type.
use epaint::mutex::Arc;
use std::any::Any;
use std::sync::Arc;
// -----------------------------------------------------------------------------------------------

View file

@ -1,8 +1,8 @@
use std::sync::Arc;
use epaint::mutex::Arc;
use crate::{
style::WidgetVisuals, text::LayoutJob, Align, Color32, Galley, Pos2, Style, TextStyle, Ui,
Visuals,
style::WidgetVisuals, text::LayoutJob, Align, Color32, Context, Galley, Pos2, Style, TextStyle,
Ui, Visuals,
};
/// Text and optional style choices for it.
@ -170,12 +170,12 @@ impl RichText {
}
/// Read the font height of the selected text style.
pub fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 {
pub fn font_height(&self, ctx: &Context) -> f32 {
let text_style = self
.text_style
.or(style.override_text_style)
.unwrap_or(style.body_text_style);
fonts.row_height(text_style)
.or(ctx.style().override_text_style)
.unwrap_or(ctx.style().body_text_style);
ctx.fonts().row_height(text_style)
}
fn into_text_job(
@ -437,10 +437,10 @@ impl WidgetText {
}
}
pub(crate) fn font_height(&self, fonts: &epaint::text::Fonts, style: &crate::Style) -> f32 {
pub(crate) fn font_height(&self, ctx: &Context) -> f32 {
match self {
Self::RichText(text) => text.font_height(fonts, style),
Self::LayoutJob(job) => job.font_height(fonts),
Self::RichText(text) => text.font_height(ctx),
Self::LayoutJob(job) => job.font_height(&*ctx.fonts()),
Self::Galley(galley) => {
if let Some(row) = galley.rows.first() {
row.height()

View file

@ -159,8 +159,8 @@ impl<'a> Widget for DragValue<'a> {
max_decimals,
} = self;
let is_slow_speed =
ui.input().modifiers.shift_only() && ui.memory().is_being_dragged(ui.next_auto_id());
let shift = ui.input().modifiers.shift_only();
let is_slow_speed = shift && ui.memory().is_being_dragged(ui.next_auto_id());
let old_value = get(&mut get_set_value);
let value = clamp_to_range(old_value, clamp_range.clone());

View file

@ -106,7 +106,7 @@ impl Label {
if let Some(first_section) = text_job.job.sections.first_mut() {
first_section.leading_space = first_row_indentation;
}
let text_galley = text_job.into_galley(ui.fonts());
let text_galley = text_job.into_galley(&*ui.fonts());
let pos = pos2(ui.max_rect().left(), ui.cursor().top());
assert!(
@ -139,7 +139,7 @@ impl Label {
text_job.job.justify = ui.layout().horizontal_justify();
};
let text_galley = text_job.into_galley(ui.fonts());
let text_galley = text_job.into_galley(&*ui.fonts());
let (rect, response) = ui.allocate_exact_size(text_galley.size(), self.sense);
let pos = match text_galley.galley.job.halign {
Align::LEFT => rect.left_top(),

View file

@ -1619,7 +1619,7 @@ fn add_rulers_and_text(
let corner_value = elem.corner_value();
shapes.push(Shape::text(
plot.ui.fonts(),
&*plot.ui.fonts(),
plot.transform.position_from_value(&corner_value) + vec2(3.0, -2.0),
Align2::LEFT_BOTTOM,
text,
@ -1674,7 +1674,7 @@ pub(super) fn rulers_at_value(
};
shapes.push(Shape::text(
plot.ui.fonts(),
&*plot.ui.fonts(),
pointer + vec2(3.0, -2.0),
Align2::LEFT_BOTTOM,
text,

View file

@ -479,7 +479,7 @@ pub struct PlotUi {
next_auto_color_idx: usize,
last_screen_transform: ScreenTransform,
response: Response,
ctx: CtxRef,
ctx: Context,
}
impl PlotUi {
@ -491,7 +491,7 @@ impl PlotUi {
Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO: OkLab or some other perspective color space
}
pub fn ctx(&self) -> &CtxRef {
pub fn ctx(&self) -> &Context {
&self.ctx
}

View file

@ -1,4 +1,4 @@
use std::sync::Arc;
use epaint::mutex::Arc;
use epaint::text::{cursor::*, Galley, LayoutJob};
@ -406,7 +406,7 @@ impl<'t> TextEdit<'t> {
let painter = ui.painter_at(text_clip_rect);
if interactive {
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
if response.hovered() && text.is_mutable() {
ui.output().mutable_text_under_cursor = true;
}
@ -665,7 +665,8 @@ fn events(
let mut any_change = false;
for event in &ui.input().events {
let events = ui.input().events.clone(); // avoid dead-lock by cloning. TODO: optimize
for event in &events {
let did_mutate_text = match event {
Event::Copy => {
if cursor_range.is_empty() {

View file

@ -4,16 +4,16 @@ use egui::epaint::TextShape;
use egui_demo_lib::LOREM_IPSUM_LONG;
pub fn criterion_benchmark(c: &mut Criterion) {
let raw_input = egui::RawInput::default();
use egui::RawInput;
{
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
let mut demo_windows = egui_demo_lib::DemoWindows::default();
// The most end-to-end benchmark.
c.bench_function("demo_with_tessellate__realistic", |b| {
b.iter(|| {
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
});
ctx.tessellate(shapes)
@ -22,13 +22,13 @@ pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("demo_no_tessellate", |b| {
b.iter(|| {
ctx.run(raw_input.clone(), |ctx| {
ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
})
})
});
let (_output, shapes) = ctx.run(raw_input.clone(), |ctx| {
let (_output, shapes) = ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
});
c.bench_function("demo_only_tessellate", |b| {
@ -37,12 +37,12 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}
if false {
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
ctx.memory().set_everything_is_visible(true); // give us everything
let mut demo_windows = egui_demo_lib::DemoWindows::default();
c.bench_function("demo_full_no_tessellate", |b| {
b.iter(|| {
ctx.run(raw_input.clone(), |ctx| {
ctx.run(RawInput::default(), |ctx| {
demo_windows.ui(ctx);
})
})
@ -50,8 +50,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
}
{
let mut ctx = egui::CtxRef::default();
let _ = ctx.run(raw_input, |ctx| {
let ctx = egui::Context::default();
let _ = ctx.run(RawInput::default(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
c.bench_function("label &str", |b| {
b.iter(|| {
@ -67,6 +67,23 @@ pub fn criterion_benchmark(c: &mut Criterion) {
});
}
{
let ctx = egui::Context::default();
ctx.begin_frame(RawInput::default());
egui::CentralPanel::default().show(&ctx, |ui| {
c.bench_function("Painter::rect", |b| {
let painter = ui.painter();
let rect = ui.max_rect();
b.iter(|| {
painter.rect(rect, 2.0, egui::Color32::RED, (1.0, egui::Color32::WHITE));
})
});
});
// Don't call `end_frame` to not have to drain the huge paint list
}
{
let pixels_per_point = 1.0;
let wrap_width = 512.0;

View file

@ -34,7 +34,7 @@ impl epi::App for ColorTest {
"🎨 Color test"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() {
ui.label(

View file

@ -16,7 +16,7 @@ impl epi::App for DemoApp {
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
@ -31,7 +31,7 @@ impl epi::App for DemoApp {
epi::set_value(storage, epi::APP_KEY, self);
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
self.demo_windows.ui(ctx);
}
}

View file

@ -26,7 +26,7 @@ impl super::Demo for CodeEditor {
"🖮 Code Editor"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View as _;
egui::Window::new(self.name())
.open(open)

View file

@ -68,7 +68,7 @@ impl super::Demo for CodeExample {
"🖮 Code Example"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View;
egui::Window::new(self.name())
.open(open)

View file

@ -47,7 +47,7 @@ impl super::Demo for ContextMenus {
"☰ Context Menus"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View;
egui::Window::new(self.name())
.vscroll(false)

View file

@ -10,7 +10,7 @@ impl super::Demo for DancingStrings {
"♫ Dancing Strings"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View file

@ -1,5 +1,5 @@
use super::Demo;
use egui::{CtxRef, ScrollArea, Ui};
use egui::{Context, ScrollArea, Ui};
use std::collections::BTreeSet;
// ----------------------------------------------------------------------------
@ -58,7 +58,7 @@ impl Demos {
}
}
pub fn windows(&mut self, ctx: &CtxRef) {
pub fn windows(&mut self, ctx: &Context) {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
@ -113,7 +113,7 @@ impl Tests {
}
}
pub fn windows(&mut self, ctx: &CtxRef) {
pub fn windows(&mut self, ctx: &Context) {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
@ -149,7 +149,7 @@ pub struct DemoWindows {
impl DemoWindows {
/// Show the app ui (menu bar and windows).
/// `sidebar_ui` can be used to optionally show some things in the sidebar
pub fn ui(&mut self, ctx: &CtxRef) {
pub fn ui(&mut self, ctx: &Context) {
let Self { demos, tests } = self;
egui::SidePanel::right("egui_demo_panel")
@ -214,7 +214,7 @@ impl DemoWindows {
}
/// Show the open windows.
fn windows(&mut self, ctx: &CtxRef) {
fn windows(&mut self, ctx: &Context) {
let Self { demos, tests } = self;
demos.windows(ctx);

View file

@ -25,7 +25,7 @@ pub fn drag_source(ui: &mut Ui, id: Id, body: impl FnOnce(&mut Ui)) {
// (anything with `Order::Tooltip` always gets an empty `Response`)
// So this is fine!
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
let delta = pointer_pos - response.rect.center();
ui.ctx().translate_layer(layer_id, delta);
}
@ -101,7 +101,7 @@ impl super::Demo for DragAndDropDemo {
"✋ Drag and Drop"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View file

@ -21,7 +21,7 @@ impl super::Demo for FontBook {
"🔤 Font Book"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);

View file

@ -76,7 +76,7 @@ impl super::Demo for LayoutTest {
"Layout Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View file

@ -31,7 +31,7 @@ impl Demo for MiscDemoWindow {
"✨ Misc Demos"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
Window::new(self.name())
.open(open)
.vscroll(true)
@ -140,7 +140,8 @@ impl Widgets {
ui.horizontal_wrapped(|ui| {
// Trick so we don't have to add spaces in the text below:
ui.spacing_mut().item_spacing.x = ui.fonts()[TextStyle::Body].glyph_width(' ');
let width = ui.fonts()[TextStyle::Body].glyph_width(' ');
ui.spacing_mut().item_spacing.x = width;
ui.label(RichText::new("Text can have").color(Color32::from_rgb(110, 255, 110)));
ui.colored_label(Color32::from_rgb(128, 140, 255), "color"); // Shortcut version

View file

@ -45,5 +45,5 @@ pub trait Demo {
fn name(&self) -> &'static str;
/// Show windows, etc
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool);
fn show(&mut self, ctx: &egui::Context, open: &mut bool);
}

View file

@ -26,7 +26,7 @@ impl super::Demo for MultiTouch {
"👌 Multi Touch"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.default_size(vec2(512.0, 512.0))
@ -77,7 +77,7 @@ impl super::View for MultiTouch {
// color and width:
let mut stroke_width = 1.;
let color = Color32::GRAY;
if let Some(multi_touch) = ui.input().multi_touch() {
if let Some(multi_touch) = ui.ctx().multi_touch() {
// This adjusts the current zoom factor and rotation angle according to the dynamic
// change (for the current frame) of the touch gesture:
self.zoom *= multi_touch.zoom_delta;

View file

@ -74,7 +74,7 @@ impl super::Demo for Painting {
"🖊 Painting"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View file

@ -647,7 +647,7 @@ impl super::Demo for PlotDemo {
"🗠 Plot"
}
fn show(&mut self, ctx: &CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &Context, open: &mut bool) {
use super::View as _;
Window::new(self.name())
.open(open)

View file

@ -29,7 +29,7 @@ impl super::Demo for Scrolling {
"↕ Scrolling"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View file

@ -36,7 +36,7 @@ impl super::Demo for Sliders {
"⬌ Sliders"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View file

@ -6,7 +6,7 @@ impl super::Demo for CursorTest {
"Cursor Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@ -38,7 +38,7 @@ impl super::Demo for IdTest {
"ID Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@ -115,7 +115,7 @@ impl super::Demo for ManualLayoutTest {
"Manual Layout Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.resizable(false)
.open(open)
@ -202,7 +202,7 @@ impl super::Demo for TableTest {
"Table Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
use super::View as _;
self.ui(ui);
@ -314,7 +314,7 @@ impl super::Demo for InputTest {
"Input Test"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)
@ -383,7 +383,7 @@ impl super::Demo for WindowResizeTest {
"↔ Window Resize"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use egui::*;
Window::new("↔ auto-sized")

View file

@ -19,7 +19,7 @@ impl super::Demo for TextEdit {
"🖹 TextEdit"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(false)

View file

@ -39,7 +39,7 @@ impl super::Demo for WidgetGallery {
"🗄 Widget Gallery"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
egui::Window::new(self.name())
.open(open)
.resizable(true)

View file

@ -36,7 +36,7 @@ impl super::Demo for WindowOptions {
"🗖 Window Options"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
let Self {
title,
title_bar,

View file

@ -7,7 +7,7 @@ impl super::Demo for WindowWithPanels {
"🗖 Window With Panels"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View as _;
let window = egui::Window::new("Window with Panels")
.default_width(600.0)

View file

@ -37,7 +37,7 @@ impl epi::App for FractalClock {
"🕑 Fractal Clock"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style()))
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));

View file

@ -67,7 +67,7 @@ impl epi::App for HttpApp {
"⬇ HTTP"
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
if let Some(receiver) = &mut self.in_progress {
// Are we there yet?
if let Ok(result) = receiver.try_recv() {

View file

@ -78,7 +78,7 @@ impl Default for BackendPanel {
}
impl BackendPanel {
pub fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
pub fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
self.frame_history
.on_new_frame(ctx.input().time, frame.info().cpu_usage);
@ -88,7 +88,7 @@ impl BackendPanel {
}
}
pub fn end_of_frame(&mut self, ctx: &egui::CtxRef) {
pub fn end_of_frame(&mut self, ctx: &egui::Context) {
self.egui_windows.windows(ctx);
}
@ -325,7 +325,7 @@ impl EguiWindows {
ui.checkbox(output_events, "📤 Output Events");
}
fn windows(&mut self, ctx: &egui::CtxRef) {
fn windows(&mut self, ctx: &egui::Context) {
let Self {
settings,
inspection,

View file

@ -34,7 +34,7 @@ impl epi::App for EasyMarkEditor {
"🖹 EasyMark editor"
}
fn update(&mut self, ctx: &egui::CtxRef, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
egui::TopBottomPanel::bottom("easy_mark_bottom").show(ctx, |ui| {
let layout = egui::Layout::top_down(egui::Align::Center).with_main_justify(true);
ui.allocate_ui_with_layout(ui.available_size(), layout, |ui| {

View file

@ -18,7 +18,8 @@ pub fn easy_mark_it<'em>(ui: &mut Ui, items: impl Iterator<Item = easy_mark::Ite
ui.allocate_ui_with_layout(initial_size, layout, |ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.set_row_height(ui.fonts()[TextStyle::Body].row_height());
let row_height = (*ui.fonts())[TextStyle::Body].row_height();
ui.set_row_height(row_height);
for item in items {
item_ui(ui, item);

View file

@ -94,7 +94,7 @@ impl FrameHistory {
let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y;
let text = format!("{:.1} ms", 1e3 * cpu_usage);
shapes.push(Shape::text(
ui.fonts(),
&*ui.fonts(),
pos2(rect.left(), y),
egui::Align2::LEFT_BOTTOM,
text,

View file

@ -140,7 +140,7 @@ Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam varius, tur
#[test]
fn test_egui_e2e() {
let mut demo_windows = crate::DemoWindows::default();
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
let raw_input = egui::RawInput::default();
const NUM_FRAMES: usize = 5;
@ -156,7 +156,7 @@ fn test_egui_e2e() {
#[test]
fn test_egui_zero_window_size() {
let mut demo_windows = crate::DemoWindows::default();
let mut ctx = egui::CtxRef::default();
let ctx = egui::Context::default();
let raw_input = egui::RawInput {
screen_rect: Some(egui::Rect::from_min_max(egui::Pos2::ZERO, egui::Pos2::ZERO)),
..Default::default()

View file

@ -44,7 +44,7 @@ impl epi::App for WrapApp {
fn setup(
&mut self,
_ctx: &egui::CtxRef,
_ctx: &egui::Context,
_frame: &epi::Frame,
_storage: Option<&dyn epi::Storage>,
) {
@ -72,7 +72,7 @@ impl epi::App for WrapApp {
cfg!(not(debug_assertions))
}
fn update(&mut self, ctx: &egui::CtxRef, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
if let Some(web_info) = frame.info().web_info.as_ref() {
if let Some(anchor) = web_info.web_location_hash.strip_prefix('#') {
self.selected_anchor = anchor.to_owned();
@ -165,7 +165,7 @@ impl WrapApp {
});
}
fn ui_file_drag_and_drop(&mut self, ctx: &egui::CtxRef) {
fn ui_file_drag_and_drop(&mut self, ctx: &egui::Context) {
use egui::*;
// Preview hovering files:

View file

@ -101,7 +101,7 @@ pub use egui_winit;
/// Convenience wrapper for using [`egui`] from a [`glium`] app.
pub struct EguiGlium {
pub egui_ctx: egui::CtxRef,
pub egui_ctx: egui::Context,
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
}
@ -129,7 +129,7 @@ impl EguiGlium {
pub fn run(
&mut self,
display: &glium::Display,
run_ui: impl FnMut(&egui::CtxRef),
run_ui: impl FnMut(&egui::Context),
) -> (bool, Vec<egui::epaint::ClippedShape>) {
let raw_input = self
.egui_winit

View file

@ -109,7 +109,7 @@ pub use epi_backend::{run, NativeOptions};
/// Use [`egui`] from a [`glow`] app.
#[cfg(feature = "winit")]
pub struct EguiGlow {
pub egui_ctx: egui::CtxRef,
pub egui_ctx: egui::Context,
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
}
@ -145,7 +145,7 @@ impl EguiGlow {
pub fn run(
&mut self,
window: &glutin::window::Window,
run_ui: impl FnMut(&egui::CtxRef),
run_ui: impl FnMut(&egui::Context),
) -> (bool, Vec<egui::epaint::ClippedShape>) {
let raw_input = self.egui_winit.take_egui_input(window);
let (egui_output, shapes) = self.egui_ctx.run(raw_input, run_ui);

View file

@ -82,7 +82,7 @@ impl epi::backend::RepaintSignal for NeedRepaint {
pub struct AppRunner {
frame: epi::Frame,
egui_ctx: egui::CtxRef,
egui_ctx: egui::Context,
painter: Box<dyn Painter>,
pub(crate) input: WebInput,
app: Box<dyn epi::App>,
@ -117,7 +117,7 @@ impl AppRunner {
repaint_signal: needs_repaint.clone(),
});
let egui_ctx = egui::CtxRef::default();
let egui_ctx = egui::Context::default();
load_memory(&egui_ctx);
if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark());
@ -151,7 +151,7 @@ impl AppRunner {
Ok(runner)
}
pub fn egui_ctx(&self) -> &egui::CtxRef {
pub fn egui_ctx(&self) -> &egui::Context {
&self.egui_ctx
}
@ -174,7 +174,7 @@ impl AppRunner {
pub fn warm_up(&mut self) -> Result<(), JsValue> {
if self.app.warm_up_enabled() {
let saved_memory = self.egui_ctx.memory().clone();
let saved_memory: egui::Memory = self.egui_ctx.memory().clone();
self.egui_ctx.memory().set_everything_is_visible(true);
self.logic()?;
*self.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge.

View file

@ -114,10 +114,10 @@ mod mutex_impl {
#[cfg(feature = "multi_threaded")]
mod rw_lock_impl {
/// The lock you get from [`RwLock::read`].
pub use parking_lot::RwLockReadGuard;
pub use parking_lot::MappedRwLockReadGuard as RwLockReadGuard;
/// The lock you get from [`RwLock::write`].
pub use parking_lot::RwLockWriteGuard;
pub use parking_lot::MappedRwLockWriteGuard as RwLockWriteGuard;
/// Provides interior mutability. Only thread-safe if the `multi_threaded` feature is enabled.
#[derive(Default)]
@ -131,16 +131,21 @@ mod rw_lock_impl {
#[inline(always)]
pub fn read(&self) -> RwLockReadGuard<'_, T> {
self.0.read()
parking_lot::RwLockReadGuard::map(self.0.read(), |v| v)
}
#[inline(always)]
pub fn write(&self) -> RwLockWriteGuard<'_, T> {
self.0.write()
parking_lot::RwLockWriteGuard::map(self.0.write(), |v| v)
}
}
}
#[cfg(feature = "multi_threaded")]
mod arc_impl {
pub use std::sync::Arc;
}
// ----------------------------------------------------------------------------
#[cfg(not(feature = "multi_threaded"))]
@ -201,8 +206,15 @@ mod rw_lock_impl {
}
}
#[cfg(not(feature = "multi_threaded"))]
mod arc_impl {
// pub use std::rc::Rc as Arc; // TODO(emilk): optimize single threaded code by using `Rc` instead of `Arc`.
pub use std::sync::Arc;
}
// ----------------------------------------------------------------------------
pub use arc_impl::Arc;
pub use mutex_impl::{Mutex, MutexGuard};
pub use rw_lock_impl::{RwLock, RwLockReadGuard, RwLockWriteGuard};

View file

@ -132,7 +132,7 @@ impl Shape {
}
#[inline]
pub fn galley(pos: Pos2, galley: std::sync::Arc<Galley>) -> Self {
pub fn galley(pos: Pos2, galley: crate::mutex::Arc<Galley>) -> Self {
TextShape::new(pos, galley).into()
}
@ -355,7 +355,7 @@ pub struct TextShape {
pub pos: Pos2,
/// The layed out text, from [`Fonts::layout_job`].
pub galley: std::sync::Arc<Galley>,
pub galley: crate::mutex::Arc<Galley>,
/// Add this underline to the whole text.
/// You can also set an underline when creating the galley.
@ -373,7 +373,7 @@ pub struct TextShape {
impl TextShape {
#[inline]
pub fn new(pos: Pos2, galley: std::sync::Arc<Galley>) -> Self {
pub fn new(pos: Pos2, galley: crate::mutex::Arc<Galley>) -> Self {
Self {
pos,
galley,

View file

@ -1,12 +1,11 @@
use crate::{
mutex::{Mutex, RwLock},
mutex::{Arc, Mutex, RwLock},
text::TextStyle,
TextureAtlas,
};
use ahash::AHashMap;
use emath::{vec2, Vec2};
use std::collections::BTreeSet;
use std::sync::Arc;
// ----------------------------------------------------------------------------

View file

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, sync::Arc};
use std::collections::BTreeMap;
use crate::{
mutex::Mutex,
mutex::{Arc, Mutex},
text::{
font::{Font, FontImpl},
Galley, LayoutJob,

View file

@ -1,8 +1,7 @@
use std::ops::RangeInclusive;
use std::sync::Arc;
use super::{Fonts, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals};
use crate::{Color32, Mesh, Stroke, Vertex};
use crate::{mutex::Arc, Color32, Mesh, Stroke, Vertex};
use emath::*;
/// Temporary storage before line-wrapping.

View file

@ -1,10 +1,9 @@
#![allow(clippy::derive_hash_xor_eq)] // We need to impl Hash for f32, but we don't implement Eq, which is fine
use std::ops::Range;
use std::sync::Arc;
use super::{cursor::*, font::UvRect};
use crate::{Color32, Mesh, Stroke, TextStyle};
use crate::{mutex::Arc, Color32, Mesh, Stroke, TextStyle};
use emath::*;
/// Describes the task of laying out text.

View file

@ -106,11 +106,11 @@ pub trait App {
///
/// Put your widgets into a [`egui::SidePanel`], [`egui::TopBottomPanel`], [`egui::CentralPanel`], [`egui::Window`] or [`egui::Area`].
///
/// The given [`egui::CtxRef`] is only valid for the duration of this call.
/// The [`Frame`] however can be cloned and saved.
/// The [`egui::Context`] and [`Frame`] can be cloned and saved if you like.
///
/// To force a repaint, call either [`egui::Context::request_repaint`] or [`Frame::request_repaint`].
fn update(&mut self, ctx: &egui::CtxRef, frame: &Frame);
/// To force a repaint, call either [`egui::Context::request_repaint`] during the call to `update`,
/// or call [`Frame::request_repaint`] at any time (e.g. from another thread).
fn update(&mut self, ctx: &egui::Context, frame: &Frame);
/// Called once before the first frame.
///
@ -118,7 +118,7 @@ pub trait App {
/// [`egui::Context::set_visuals`] etc.
///
/// Also allows you to restore state, if there is a storage (required the "persistence" feature).
fn setup(&mut self, _ctx: &egui::CtxRef, _frame: &Frame, _storage: Option<&dyn Storage>) {}
fn setup(&mut self, _ctx: &egui::Context, _frame: &Frame, _storage: Option<&dyn Storage>) {}
/// If `true` a warm-up call to [`Self::update`] will be issued where
/// `ctx.memory().everything_is_visible()` will be set to `true`.