use std::sync::{
atomic::{AtomicU32, Ordering::SeqCst},
Arc,
};
use {
ahash::AHashMap,
parking_lot::{Mutex, MutexGuard},
};
use crate::{animation_manager::AnimationManager, paint::*, *};
#[derive(Clone, Copy, Default)]
struct PaintStats {
num_jobs: usize,
num_primitives: usize,
num_vertices: usize,
num_triangles: usize,
}
#[derive(Default)]
pub struct Context {
style: Mutex<Style>,
paint_options: Mutex<paint::PaintOptions>,
fonts: Option<Arc<Fonts>>,
font_definitions: Mutex<FontDefinitions>,
memory: Arc<Mutex<Memory>>,
animation_manager: Arc<Mutex<AnimationManager>>,
input: InputState,
graphics: Mutex<GraphicLayers>,
output: Mutex<Output>,
used_ids: Mutex<AHashMap<Id, Pos2>>,
paint_stats: Mutex<PaintStats>,
repaint_requests: AtomicU32,
}
impl Clone for Context {
fn clone(&self) -> Self {
Context {
style: Mutex::new(self.style()),
paint_options: Mutex::new(*self.paint_options.lock()),
fonts: self.fonts.clone(),
font_definitions: Mutex::new(self.font_definitions.lock().clone()),
memory: self.memory.clone(),
animation_manager: self.animation_manager.clone(),
input: self.input.clone(),
graphics: Mutex::new(self.graphics.lock().clone()),
output: Mutex::new(self.output.lock().clone()),
used_ids: Mutex::new(self.used_ids.lock().clone()),
paint_stats: Mutex::new(*self.paint_stats.lock()),
repaint_requests: self.repaint_requests.load(SeqCst).into(),
}
}
}
impl Context {
pub fn new() -> Arc<Self> {
Arc::new(Self::default())
}
pub fn rect(&self) -> Rect {
Rect::from_min_size(pos2(0.0, 0.0), self.input.screen_size)
}
pub fn memory(&self) -> MutexGuard<'_, Memory> {
lock(&self.memory, "memory")
}
pub fn graphics(&self) -> MutexGuard<'_, GraphicLayers> {
lock(&self.graphics, "graphics")
}
pub fn output(&self) -> MutexGuard<'_, Output> {
lock(&self.output, "output")
}
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 Context::begin_frame()`")
}
pub fn texture(&self) -> &paint::Texture {
self.fonts().texture()
}
pub fn set_fonts(&self, font_definitions: FontDefinitions) {
*self.font_definitions.lock() = font_definitions;
}
pub fn style(&self) -> Style {
lock(&self.style, "style").clone()
}
pub fn set_style(&self, style: Style) {
*lock(&self.style, "style") = style;
}
pub fn pixels_per_point(&self) -> f32 {
self.input.pixels_per_point
}
pub fn round_to_pixel(&self, point: f32) -> f32 {
(point * self.input.pixels_per_point).round() / self.input.pixels_per_point
}
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
}
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
}
pub 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 fn begin_frame(self: &mut Arc<Self>, new_input: RawInput) -> Ui {
let mut self_: Self = (**self).clone();
self_.begin_frame_mut(new_input);
*self = Arc::new(self_);
self.fullscreen_ui()
}
fn begin_frame_mut(&mut self, new_raw_input: RawInput) {
self.memory().begin_frame(&self.input);
self.used_ids.lock().clear();
self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input);
let mut font_definitions = self.font_definitions.lock();
font_definitions.pixels_per_point = self.input.pixels_per_point;
if self.fonts.is_none() || *self.fonts.as_ref().unwrap().definitions() != *font_definitions
{
self.fonts = Some(Arc::new(Fonts::from_definitions(font_definitions.clone())));
}
}
#[must_use]
pub fn end_frame(&self) -> (Output, PaintJobs) {
if self.input.wants_repaint() {
self.request_repaint();
}
self.memory().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);
output.needs_repaint = true;
}
let paint_jobs = self.paint();
(output, paint_jobs)
}
fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
let memory = self.memory();
self.graphics().drain(memory.areas.order()).collect()
}
fn paint(&self) -> PaintJobs {
let mut paint_options = *self.paint_options.lock();
paint_options.aa_size = 1.0 / self.pixels_per_point();
let paint_commands = self.drain_paint_lists();
let num_primitives = paint_commands.len();
let paint_jobs =
tessellator::tessellate_paint_commands(paint_commands, paint_options, self.fonts());
{
let mut stats = PaintStats::default();
stats.num_jobs = paint_jobs.len();
stats.num_primitives = num_primitives;
for (_, triangles) in &paint_jobs {
stats.num_vertices += triangles.vertices.len();
stats.num_triangles += triangles.indices.len() / 3;
}
*self.paint_stats.lock() = stats;
}
paint_jobs
}
fn fullscreen_ui(self: &Arc<Self>) -> Ui {
let rect = Rect::from_min_size(Default::default(), self.input().screen_size);
let id = Id::background();
let layer = Layer {
order: Order::Background,
id,
};
self.memory().areas.set_state(
layer,
containers::area::State {
pos: rect.min,
size: rect.size(),
interactable: true,
vel: Default::default(),
},
);
Ui::new(self.clone(), layer, id, rect)
}
pub fn make_unique_id<IdSource>(self: &Arc<Self>, source: IdSource, pos: Pos2) -> Id
where
IdSource: std::hash::Hash + std::fmt::Debug + Copy,
{
self.register_unique_id(Id::new(source), source, pos)
}
pub fn is_unique_id(&self, id: Id) -> bool {
!self.used_ids.lock().contains_key(&id)
}
pub fn register_unique_id(
self: &Arc<Self>,
id: Id,
source_name: impl std::fmt::Debug,
pos: Pos2,
) -> Id {
if let Some(clash_pos) = self.used_ids.lock().insert(id, pos) {
let painter = self.debug_painter();
if clash_pos.distance(pos) < 4.0 {
painter.error(
pos,
&format!("use of non-unique ID {:?} (name clash?)", source_name),
);
} else {
painter.error(
clash_pos,
&format!("first use of non-unique ID {:?} (name clash?)", source_name),
);
painter.error(
pos,
&format!(
"second use of non-unique ID {:?} (name clash?)",
source_name
),
);
}
id
} else {
id
}
}
pub fn is_mouse_over_area(&self) -> bool {
if let Some(mouse_pos) = self.input.mouse.pos {
if let Some(layer) = self.layer_at(mouse_pos) {
layer.order != Order::Background
} else {
false
}
} else {
false
}
}
pub fn wants_mouse_input(&self) -> bool {
self.is_mouse_over_area() || self.is_using_mouse()
}
pub fn is_using_mouse(&self) -> bool {
self.memory().interaction.is_using_mouse()
}
pub fn wants_keyboard_input(&self) -> bool {
self.memory().interaction.kb_focus_id.is_some()
}
pub fn layer_at(&self, pos: Pos2) -> Option<Layer> {
let resize_grab_radius_side = self.style().interaction.resize_grab_radius_side;
self.memory().layer_at(pos, resize_grab_radius_side)
}
pub fn contains_mouse(&self, layer: Layer, clip_rect: Rect, rect: Rect) -> bool {
let rect = rect.intersect(clip_rect);
if let Some(mouse_pos) = self.input.mouse.pos {
rect.contains(mouse_pos) && self.layer_at(mouse_pos) == Some(layer)
} else {
false
}
}
pub(crate) fn interact(
self: &Arc<Self>,
layer: Layer,
clip_rect: Rect,
rect: Rect,
interaction_id: Option<Id>,
sense: Sense,
) -> Response {
let interact_rect = rect.expand2(0.5 * self.style().spacing.item_spacing); let hovered = self.contains_mouse(layer, clip_rect, interact_rect);
let has_kb_focus = interaction_id
.map(|id| self.memory().has_kb_focus(id))
.unwrap_or(false);
if interaction_id.is_none() || sense == Sense::nothing() {
return Response {
ctx: self.clone(),
sense,
rect,
hovered,
clicked: false,
double_clicked: false,
active: false,
has_kb_focus,
};
}
let interaction_id = interaction_id.unwrap();
let mut memory = self.memory();
memory.interaction.click_interest |= hovered && sense.click;
memory.interaction.drag_interest |= hovered && sense.drag;
let active = memory.interaction.click_id == Some(interaction_id)
|| memory.interaction.drag_id == Some(interaction_id);
if self.input.mouse.pressed {
if hovered {
let mut response = Response {
ctx: self.clone(),
sense,
rect,
hovered: true,
clicked: false,
double_clicked: false,
active: false,
has_kb_focus,
};
if sense.click && memory.interaction.click_id.is_none() {
memory.interaction.click_id = Some(interaction_id);
response.active = true;
}
if sense.drag
&& (memory.interaction.drag_id.is_none() || memory.interaction.drag_is_window)
{
memory.interaction.drag_id = Some(interaction_id);
memory.interaction.drag_is_window = false;
memory.window_interaction = None; response.active = true;
}
response
} else {
Response {
ctx: self.clone(),
sense,
rect,
hovered,
clicked: false,
double_clicked: false,
active: false,
has_kb_focus,
}
}
} else if self.input.mouse.released {
let clicked = hovered && active && self.input.mouse.could_be_click;
Response {
ctx: self.clone(),
sense,
rect,
hovered,
clicked,
double_clicked: clicked && self.input.mouse.double_click,
active,
has_kb_focus,
}
} else if self.input.mouse.down {
Response {
ctx: self.clone(),
sense,
rect,
hovered: hovered && active,
clicked: false,
double_clicked: false,
active,
has_kb_focus,
}
} else {
Response {
ctx: self.clone(),
sense,
rect,
hovered,
clicked: false,
double_clicked: false,
active,
has_kb_focus,
}
}
}
}
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
}
}
impl Context {
pub fn debug_painter(self: &Arc<Self>) -> Painter {
Painter::new(self.clone(), Layer::debug(), self.rect())
}
}
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| {
self.paint_options.lock().ui(ui);
});
}
pub fn inspection_ui(&self, ui: &mut Ui) {
use crate::containers::*;
ui.style_mut().body_text_style = TextStyle::Monospace;
CollapsingHeader::new("Input")
.default_open(true)
.show(ui, |ui| ui.input().clone().ui(ui));
ui.collapsing("Stats", |ui| {
ui.add(label!(
"Screen size: {} x {} points, pixels_per_point: {}",
ui.input().screen_size.x,
ui.input().screen_size.y,
ui.input().pixels_per_point,
));
ui.add(label!("Painting:").text_style(TextStyle::Heading));
self.paint_stats.lock().ui(ui);
});
}
pub fn memory_ui(&self, ui: &mut crate::Ui) {
use crate::widgets::*;
if ui
.add(Button::new("Reset all"))
.tooltip_text("Reset all Egui state")
.clicked
{
*self.memory() = Default::default();
}
ui.horizontal(|ui| {
ui.add(label!(
"{} areas (window positions)",
self.memory().areas.count()
));
if ui.add(Button::new("Reset")).clicked {
self.memory().areas = Default::default();
}
});
ui.horizontal(|ui| {
ui.add(label!(
"{} collapsing headers",
self.memory().collapsing_headers.len()
));
if ui.add(Button::new("Reset")).clicked {
self.memory().collapsing_headers = Default::default();
}
});
ui.horizontal(|ui| {
ui.add(label!("{} menu bars", self.memory().menu_bar.len()));
if ui.add(Button::new("Reset")).clicked {
self.memory().menu_bar = Default::default();
}
});
ui.horizontal(|ui| {
ui.add(label!("{} scroll areas", self.memory().scroll_areas.len()));
if ui.add(Button::new("Reset")).clicked {
self.memory().scroll_areas = Default::default();
}
});
ui.horizontal(|ui| {
ui.add(label!("{} resize areas", self.memory().resize.len()));
if ui.add(Button::new("Reset")).clicked {
self.memory().resize = Default::default();
}
});
ui.add(
label!("NOTE: the position of this window cannot be reset from within itself.")
.auto_shrink(),
);
}
}
impl Context {
pub fn style_ui(&self, ui: &mut Ui) {
let mut style = self.style();
style.ui(ui);
self.set_style(style);
}
}
impl paint::PaintOptions {
pub fn ui(&mut self, ui: &mut Ui) {
use crate::widgets::*;
ui.add(Checkbox::new(&mut self.anti_alias, "Antialias"));
ui.add(Checkbox::new(
&mut self.debug_paint_clip_rects,
"Paint Clip Rects (debug)",
));
}
}
impl PaintStats {
pub fn ui(&self, ui: &mut Ui) {
ui.add(label!("Jobs: {}", self.num_jobs))
.tooltip_text("Number of separate clip rectangles");
ui.add(label!("Primitives: {}", self.num_primitives))
.tooltip_text("Boxes, circles, text areas etc");
ui.add(label!("Vertices: {}", self.num_vertices));
ui.add(label!("Triangles: {}", self.num_triangles));
}
}
#[cfg(debug_assertions)]
fn lock<'m, T>(mutex: &'m Mutex<T>, what: &'static str) -> MutexGuard<'m, T> {
mutex
.try_lock()
.unwrap_or_else(|| panic!("The Mutex for {} is already locked. Probably a bug", what))
}
#[cfg(not(debug_assertions))]
fn lock<'m, T>(mutex: &'m Mutex<T>, _what: &'static str) -> MutexGuard<'m, T> {
mutex.lock()
}