use std::sync::{
atomic::{AtomicU32, Ordering::SeqCst},
Arc,
};
use crate::{
animation_manager::AnimationManager,
data::output::Output,
frame_state::FrameState,
input_state::*,
layers::GraphicLayers,
mutex::{Mutex, MutexGuard},
*,
};
use epaint::{stats::*, text::Fonts, *};
#[derive(Clone)]
pub struct CtxRef(std::sync::Arc<Context>);
impl std::ops::Deref for CtxRef {
type Target = Context;
fn deref(&self) -> &Context {
self.0.deref()
}
}
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 {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Default for CtxRef {
fn default() -> Self {
Self(Arc::new(Context {
repaint_requests: AtomicU32::new(1),
..Context::default()
}))
}
}
impl CtxRef {
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(crate) fn register_interaction_id(&self, id: Id, new_pos: Pos2) {
let prev_pos = self.frame_state().used_ids.insert(id, new_pos);
if let Some(prev_pos) = prev_pos {
if prev_pos.distance(new_pos) < 0.1 {
return;
}
let show_error = |pos: Pos2, text: String| {
let painter = self.debug_painter();
let rect = painter.error(pos, text);
if let Some(pointer_pos) = self.input.pointer.tooltip_pos() {
if rect.contains(pointer_pos) {
painter.error(
rect.left_bottom() + vec2(2.0, 4.0),
"ID clashes happens when things like Windows or CollpasingHeaders share names,\n\
or when things like ScrollAreas and Resize areas aren't given unique id_source:s.",
);
}
}
};
let id_str = id.short_debug_format();
if prev_pos.distance(new_pos) < 4.0 {
show_error(new_pos, format!("Double use of ID {}", id_str));
} else {
show_error(prev_pos, format!("First use of ID {}", id_str));
show_error(new_pos, format!("Second use of ID {}", id_str));
}
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn interact(
&self,
clip_rect: Rect,
item_spacing: Vec2,
layer_id: LayerId,
id: Id,
rect: Rect,
sense: Sense,
enabled: bool,
) -> Response {
let gap = 0.5;
let interact_rect = rect.expand2(
(0.5 * item_spacing - Vec2::splat(gap))
.at_least(Vec2::splat(0.0))
.at_most(Vec2::splat(5.0)),
);
let hovered = self.rect_contains_pointer(layer_id, clip_rect.intersect(interact_rect));
self.interact_with_hovered(layer_id, id, rect, sense, enabled, hovered)
}
pub(crate) fn interact_with_hovered(
&self,
layer_id: LayerId,
id: Id,
rect: Rect,
sense: Sense,
enabled: bool,
hovered: bool,
) -> Response {
let hovered = hovered && enabled;
let has_kb_focus = self.memory().has_kb_focus(id);
let lost_kb_focus = self.memory().lost_kb_focus(id);
let mut response = Response {
ctx: self.clone(),
layer_id,
id,
rect,
sense,
enabled,
hovered,
clicked: Default::default(),
double_clicked: Default::default(),
dragged: false,
drag_released: false,
is_pointer_button_down_on: false,
interact_pointer_pos: None,
has_kb_focus,
lost_kb_focus,
changed: false,
};
if !enabled || sense == Sense::hover() || !layer_id.allow_interaction() {
return response;
}
self.register_interaction_id(id, rect.min);
let mut memory = self.memory();
memory.interaction.click_interest |= hovered && sense.click;
memory.interaction.drag_interest |= hovered && sense.drag;
response.dragged = memory.interaction.drag_id == Some(id);
response.is_pointer_button_down_on =
memory.interaction.click_id == Some(id) || response.dragged;
for pointer_event in &self.input.pointer.pointer_events {
match pointer_event {
PointerEvent::Moved(_) => {}
PointerEvent::Pressed(_) => {
if hovered {
if sense.click && memory.interaction.click_id.is_none() {
memory.interaction.click_id = Some(id);
response.is_pointer_button_down_on = true;
}
if sense.drag
&& (memory.interaction.drag_id.is_none()
|| memory.interaction.drag_is_window)
{
memory.interaction.drag_id = Some(id);
memory.interaction.drag_is_window = false;
memory.window_interaction = None;
response.is_pointer_button_down_on = true;
response.dragged = true;
}
}
}
PointerEvent::Released(click) => {
response.drag_released = response.dragged;
response.dragged = false;
if hovered && response.is_pointer_button_down_on {
if let Some(click) = click {
let clicked = hovered && response.is_pointer_button_down_on;
response.clicked[click.button as usize] = clicked;
response.double_clicked[click.button as usize] =
clicked && click.is_double();
}
}
}
}
}
if response.is_pointer_button_down_on {
response.interact_pointer_pos = self.input().pointer.interact_pos();
}
if self.input.pointer.any_down() {
response.hovered &= response.is_pointer_button_down_on;
}
response
}
pub fn debug_painter(&self) -> Painter {
Painter::new(self.clone(), LayerId::debug(), self.input.screen_rect())
}
}
#[derive(Default)]
pub struct Context {
fonts: Option<Arc<Fonts>>,
memory: Arc<Mutex<Memory>>,
animation_manager: Arc<Mutex<AnimationManager>>,
input: InputState,
frame_state: Mutex<FrameState>,
graphics: Mutex<GraphicLayers>,
output: Mutex<Output>,
paint_stats: Mutex<PaintStats>,
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(),
}
}
}
impl Context {
#[allow(clippy::new_ret_no_self)]
#[deprecated = "Use CtxRef::default() instead"]
pub fn new() -> CtxRef {
CtxRef::default()
}
pub fn available_rect(&self) -> Rect {
self.frame_state.lock().available_rect()
}
pub fn memory(&self) -> MutexGuard<'_, Memory> {
self.memory.lock()
}
pub(crate) fn graphics(&self) -> MutexGuard<'_, GraphicLayers> {
self.graphics.lock()
}
pub fn output(&self) -> MutexGuard<'_, Output> {
self.output.lock()
}
pub(crate) fn frame_state(&self) -> MutexGuard<'_, FrameState> {
self.frame_state.lock()
}
pub fn request_repaint(&self) {
let times_to_repaint = 2;
self.repaint_requests.store(times_to_repaint, SeqCst);
}
pub fn input(&self) -> &InputState {
&self.input
}
pub fn fonts(&self) -> &Fonts {
&*self
.fonts
.as_ref()
.expect("No fonts available until first call to CtxRef::begin_frame()")
}
pub fn texture(&self) -> Arc<epaint::Texture> {
self.fonts().texture()
}
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
self.memory().options.font_definitions = font_definitions;
}
pub fn style(&self) -> Arc<Style> {
self.memory().options.style.clone()
}
pub fn set_style(&self, style: impl Into<Arc<Style>>) {
self.memory().options.style = style.into();
}
pub fn set_visuals(&self, visuals: crate::Visuals) {
std::sync::Arc::make_mut(&mut self.memory().options.style).visuals = visuals;
}
pub fn pixels_per_point(&self) -> f32 {
self.input.pixels_per_point()
}
pub fn set_pixels_per_point(&self, pixels_per_point: f32) {
self.memory().new_pixels_per_point = Some(pixels_per_point);
}
pub(crate) fn round_to_pixel(&self, point: f32) -> f32 {
let pixels_per_point = self.pixels_per_point();
(point * pixels_per_point).round() / pixels_per_point
}
pub(crate) fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
}
pub(crate) fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
}
pub(crate) fn round_rect_to_pixels(&self, rect: Rect) -> Rect {
Rect {
min: self.round_pos_to_pixels(rect.min),
max: self.round_pos_to_pixels(rect.max),
}
}
pub(crate) fn constrain_window_rect(&self, window: Rect) -> Rect {
let mut screen = self.available_rect();
if window.width() > screen.width() {
screen.max.x = self.input().screen_rect().max.x;
screen.min.x = self.input().screen_rect().min.x;
}
if window.height() > screen.height() {
screen.max.y = self.input().screen_rect().max.y;
screen.min.y = self.input().screen_rect().min.y;
}
let mut pos = window.min;
let margin_x = (window.width() - screen.width()).at_least(0.0);
let margin_y = (window.height() - screen.height()).at_least(0.0);
pos.x = pos.x.at_most(screen.right() + margin_x - window.width());
pos.x = pos.x.at_least(screen.left() - margin_x);
pos.y = pos.y.at_most(screen.bottom() + margin_y - window.height());
pos.y = pos.y.at_least(screen.top() - margin_y);
pos = self.round_pos_to_pixels(pos);
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);
let font_definitions = self.memory().options.font_definitions.clone();
let pixels_per_point = self.input.pixels_per_point();
let same_as_current = match &self.fonts {
None => false,
Some(fonts) => {
*fonts.definitions() == font_definitions
&& (fonts.pixels_per_point() - pixels_per_point).abs() < 1e-3
}
};
if !same_as_current {
self.fonts = Some(Arc::new(Fonts::from_definitions(
pixels_per_point,
font_definitions,
)));
}
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,
},
);
}
#[must_use]
pub fn end_frame(&self) -> (Output, Vec<ClippedShape>) {
if self.input.wants_repaint() {
self.request_repaint();
}
self.memory()
.end_frame(&self.input, &self.frame_state().used_ids);
let mut output: Output = std::mem::take(&mut self.output());
if self.repaint_requests.load(SeqCst) > 0 {
self.repaint_requests.fetch_sub(1, SeqCst);
output.needs_repaint = true;
}
let shapes = self.drain_paint_lists();
(output, shapes)
}
fn drain_paint_lists(&self) -> Vec<ClippedShape> {
let memory = self.memory();
self.graphics().drain(memory.areas.order()).collect()
}
pub fn tessellate(&self, shapes: Vec<ClippedShape>) -> Vec<ClippedMesh> {
let mut tessellation_options = self.memory().options.tessellation_options;
tessellation_options.aa_size = 1.0 / self.pixels_per_point();
let paint_stats = PaintStats::from_shapes(&shapes);
let clipped_meshes =
tessellator::tessellate_shapes(shapes, tessellation_options, self.fonts());
*self.paint_stats.lock() = paint_stats.with_clipped_meshes(&clipped_meshes);
clipped_meshes
}
pub fn used_rect(&self) -> Rect {
let mut used = self.frame_state().used_by_panels;
for window in self.memory().areas.visible_windows() {
used = used.union(window.rect());
}
used
}
pub fn used_size(&self) -> Vec2 {
self.used_rect().max - Pos2::new(0.0, 0.0)
}
pub fn is_pointer_over_area(&self) -> bool {
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
if let Some(layer) = self.layer_id_at(pointer_pos) {
if layer.order == Order::Background {
!self.frame_state().unused_rect.contains(pointer_pos)
} else {
true
}
} else {
false
}
} else {
false
}
}
pub fn wants_pointer_input(&self) -> bool {
self.is_using_pointer() || (self.is_pointer_over_area() && !self.input().pointer.any_down())
}
pub fn is_using_pointer(&self) -> bool {
self.memory().interaction.is_using_pointer()
}
#[deprecated = "Renamed wants_pointer_input"]
pub fn wants_mouse_input(&self) -> bool {
self.wants_pointer_input()
}
#[deprecated = "Renamed is_using_pointer"]
pub fn is_using_mouse(&self) -> bool {
self.is_using_pointer()
}
pub fn wants_keyboard_input(&self) -> bool {
self.memory().interaction.kb_focus_id.is_some()
}
pub fn translate_layer(&self, layer_id: LayerId, delta: Vec2) {
self.graphics().list(layer_id).translate(delta);
}
pub fn layer_id_at(&self, pos: Pos2) -> Option<LayerId> {
let resize_grab_radius_side = self.style().interaction.resize_grab_radius_side;
self.memory().layer_id_at(pos, resize_grab_radius_side)
}
pub(crate) fn rect_contains_pointer(&self, layer_id: LayerId, rect: Rect) -> bool {
if let Some(pointer_pos) = self.input.pointer.interact_pos() {
rect.contains(pointer_pos) && self.layer_id_at(pointer_pos) == Some(layer_id)
} else {
false
}
}
}
impl Context {
pub fn animate_bool(&self, id: Id, value: bool) -> f32 {
let animation_time = self.style().animation_time;
let animated_value =
self.animation_manager
.lock()
.animate_bool(&self.input, animation_time, id, value);
let animation_in_progress = 0.0 < animated_value && animated_value < 1.0;
if animation_in_progress {
self.request_repaint();
}
animated_value
}
pub fn clear_animations(&self) {
*self.animation_manager.lock() = Default::default();
}
}
impl Context {
pub fn settings_ui(&self, ui: &mut Ui) {
use crate::containers::*;
CollapsingHeader::new("🎑 Style")
.default_open(true)
.show(ui, |ui| {
self.style_ui(ui);
});
CollapsingHeader::new("🔠 Fonts")
.default_open(false)
.show(ui, |ui| {
let mut font_definitions = self.fonts().definitions().clone();
font_definitions.ui(ui);
self.fonts().texture().ui(ui);
self.set_fonts(font_definitions);
});
CollapsingHeader::new("✒ Painting")
.default_open(true)
.show(ui, |ui| {
let mut tessellation_options = self.memory().options.tessellation_options;
tessellation_options.ui(ui);
self.memory().options.tessellation_options = tessellation_options;
});
}
pub fn inspection_ui(&self, ui: &mut Ui) {
use crate::containers::*;
ui.label(format!("Is using pointer: {}", self.is_using_pointer()))
.on_hover_text(
"Is egui currently using the pointer actively (e.g. dragging a slider)?",
);
ui.label(format!("Wants pointer input: {}", self.wants_pointer_input()))
.on_hover_text("Is egui currently interested in the location of the pointer (either because it is in use, or because it is hovering over a window).");
ui.label(format!(
"Wants keyboard input: {}",
self.wants_keyboard_input()
))
.on_hover_text("Is egui currently listening for text input");
ui.advance_cursor(16.0);
CollapsingHeader::new("📥 Input")
.default_open(false)
.show(ui, |ui| ui.input().clone().ui(ui));
CollapsingHeader::new("📊 Paint stats")
.default_open(true)
.show(ui, |ui| {
self.paint_stats.lock().ui(ui);
});
}
pub fn memory_ui(&self, ui: &mut crate::Ui) {
if ui
.button("Reset all")
.on_hover_text("Reset all egui state")
.clicked()
{
*self.memory() = Default::default();
}
ui.horizontal(|ui| {
ui.label(format!(
"{} areas (window positions)",
self.memory().areas.count()
));
if ui.button("Reset").clicked() {
self.memory().areas = Default::default();
}
});
ui.indent("areas", |ui| {
let layers_ids: Vec<LayerId> = self.memory().areas.order().to_vec();
for layer_id in layers_ids {
let area = self.memory().areas.get(layer_id.id).cloned();
if let Some(area) = area {
let is_visible = self.memory().areas.is_visible(&layer_id);
if ui
.label(format!(
"{:?} {:?} {}",
layer_id.order,
area.rect(),
if is_visible { "" } else { "(INVISIBLE)" }
))
.hovered
&& is_visible
{
ui.ctx()
.debug_painter()
.debug_rect(area.rect(), Color32::RED, "");
}
}
}
});
ui.horizontal(|ui| {
ui.label(format!(
"{} collapsing headers",
self.memory().collapsing_headers.len()
));
if ui.button("Reset").clicked() {
self.memory().collapsing_headers = Default::default();
}
});
ui.horizontal(|ui| {
ui.label(format!("{} menu bars", self.memory().menu_bar.len()));
if ui.button("Reset").clicked() {
self.memory().menu_bar = Default::default();
}
});
ui.horizontal(|ui| {
ui.label(format!("{} scroll areas", self.memory().scroll_areas.len()));
if ui.button("Reset").clicked() {
self.memory().scroll_areas = Default::default();
}
});
ui.horizontal(|ui| {
ui.label(format!("{} resize areas", self.memory().resize.len()));
if ui.button("Reset").clicked() {
self.memory().resize = Default::default();
}
});
ui.shrink_width_to_current();
ui.label("NOTE: the position of this window cannot be reset from within itself.");
ui.collapsing("Interaction", |ui| {
let interaction = self.memory().interaction.clone();
interaction.ui(ui);
});
}
}
impl Context {
pub fn style_ui(&self, ui: &mut Ui) {
let mut style: Style = (*self.style()).clone();
style.ui(ui);
self.set_style(style);
}
}