1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 13:50:04 -04:00

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
75 changed files with 550 additions and 495 deletions

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() {