Compare commits

...

7 commits

Author SHA1 Message Date
Emil Ernerfeldt
238581c65e wip 2021-12-28 20:54:26 +01:00
Emil Ernerfeldt
19264c8043 Cleanup CtxRef::run with CtxRef::mutate helper 2021-12-28 20:49:18 +01:00
Emil Ernerfeldt
360b83e8f7 Do two passes if the option is enabled 2021-12-28 20:49:18 +01:00
Emil Ernerfeldt
e0a8e8f36a Add multipass option and checkbox 2021-12-28 20:49:18 +01:00
Emil Ernerfeldt
42110f8cd5 Refactor: split out event processing in the input state 2021-12-28 20:49:18 +01:00
Emil Ernerfeldt
66eb2fb5f7 Rename Fonts::end_frame to Fonts::prune_cache 2021-12-28 20:49:18 +01:00
Emil Ernerfeldt
d8519b3281 Make begin_frame methods on input non-pub 2021-12-28 20:49:18 +01:00
10 changed files with 193 additions and 91 deletions

View file

@ -19,6 +19,15 @@ use epaint::{stats::*, text::Fonts, *};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PassState {
SinglePass,
SizePass,
LayoutPass,
}
// ----------------------------------------------------------------------------
/// A wrapper around [`Arc`](std::sync::Arc)`<`[`Context`]`>`. /// A wrapper around [`Arc`](std::sync::Arc)`<`[`Context`]`>`.
/// This is how you will normally create and access a [`Context`]. /// This is how you will normally create and access a [`Context`].
/// ///
@ -102,18 +111,101 @@ impl CtxRef {
#[must_use] #[must_use]
pub fn run( pub fn run(
&mut self, &mut self,
new_input: RawInput, new_raw_input: RawInput,
run_ui: impl FnOnce(&CtxRef), run_ui: impl Fn(&CtxRef),
) -> (Output, Vec<ClippedShape>) { ) -> (Output, Vec<ClippedShape>) {
let mut self_: Context = (*self.0).clone(); self.memory().begin_frame(&self.input, &new_raw_input);
self_.begin_frame_mut(new_input);
*self = Self(Arc::new(self_)); self.mutate(|context| {
if let Some(new_pixels_per_point) = context.memory.lock().new_pixels_per_point.take() {
context.input.pixels_per_point = new_pixels_per_point;
}
context.input.begin_frame(&new_raw_input);
context.update_fonts(context.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,
},
);
if self.memory().options.multi_pass {
self.frame_state.lock().begin_pass(&self.input);
self.mutate(|context| {
context.pass_state = Some(PassState::SizePass);
});
run_ui(self); run_ui(self);
self.drain_paint_lists();
self.mutate(|context| {
context.input.on_events(new_raw_input);
context.pass_state = Some(PassState::LayoutPass);
});
self.frame_state.lock().begin_pass(&self.input);
run_ui(self);
} else {
self.mutate(|context| {
context.input.on_events(new_raw_input);
context.pass_state = Some(PassState::SinglePass);
});
}
self.mutate(|context| {
if let Some(new_pixels_per_point) = context.memory.lock().new_pixels_per_point.take() {
context.input.pixels_per_point = new_pixels_per_point;
}
context.input.begin_frame(&new_raw_input);
context.update_fonts(context.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,
},
);
if self.memory().options.multi_pass {
self.frame_state.lock().begin_pass(&self.input);
run_ui(self);
self.drain_paint_lists();
self.mutate(|context| context.input.on_events(new_raw_input));
self.frame_state.lock().begin_pass(&self.input);
run_ui(self);
} else {
self.mutate(|context| context.input.on_events(new_raw_input));
self.frame_state.lock().begin_pass(&self.input);
run_ui(self);
}
self.mutate(|context| {
context.pass_state = None;
});
self.end_frame() self.end_frame()
} }
fn mutate(&mut self, mutate: impl FnOnce(&mut Context)) {
let mut self_: Context = (*self.0).clone();
mutate(&mut self_);
*self = Self(Arc::new(self_));
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
/// If the given [`Id`] is not unique, an error will be printed at the given position. /// If the given [`Id`] is not unique, an error will be printed at the given position.
@ -355,6 +447,8 @@ pub struct Context {
/// While positive, keep requesting repaints. Decrement at the end of each frame. /// While positive, keep requesting repaints. Decrement at the end of each frame.
repaint_requests: AtomicU32, repaint_requests: AtomicU32,
pub(crate) pass_state: Option<PassState>,
} }
impl Clone for Context { impl Clone for Context {
@ -363,13 +457,14 @@ impl Clone for Context {
fonts: self.fonts.clone(), fonts: self.fonts.clone(),
memory: self.memory.clone(), memory: self.memory.clone(),
animation_manager: self.animation_manager.clone(), animation_manager: self.animation_manager.clone(),
context_menu_system: self.context_menu_system.clone(),
input: self.input.clone(), input: self.input.clone(),
frame_state: self.frame_state.clone(), frame_state: self.frame_state.clone(),
graphics: self.graphics.clone(), graphics: self.graphics.clone(),
output: self.output.clone(), output: self.output.clone(),
paint_stats: self.paint_stats.clone(), paint_stats: self.paint_stats.clone(),
repaint_requests: self.repaint_requests.load(SeqCst).into(), repaint_requests: self.repaint_requests.load(SeqCst).into(),
context_menu_system: self.context_menu_system.clone(), pass_state: None,
} }
} }
} }
@ -566,31 +661,6 @@ impl Context {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
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. /// Load fonts unless already loaded.
fn update_fonts(&mut self, pixels_per_point: f32) { fn update_fonts(&mut self, pixels_per_point: f32) {
let new_font_definitions = self.memory().new_font_definitions.take(); let new_font_definitions = self.memory().new_font_definitions.take();
@ -627,7 +697,7 @@ impl Context {
self.memory() self.memory()
.end_frame(&self.input, &self.frame_state().used_ids); .end_frame(&self.input, &self.frame_state().used_ids);
self.fonts().end_frame(); self.fonts().prune_cache();
let mut output: Output = std::mem::take(&mut self.output()); let mut output: Output = std::mem::take(&mut self.output());
if self.repaint_requests.load(SeqCst) > 0 { if self.repaint_requests.load(SeqCst) > 0 {

View file

@ -1,10 +1,12 @@
use crate::*; use crate::*;
/// State that is collected during a frame and then cleared. // TODO: rename `FrameState` -> `PassState` ?
/// Short-term (single frame) memory. /// State that is collected during a pass and then cleared.
///
/// One frame consists of either one or two passes.
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct FrameState { pub(crate) struct FrameState {
/// All `Id`s that were used this frame. /// All `Id`s that were used this pass.
/// Used to debug `Id` clashes of widgets. /// Used to debug `Id` clashes of widgets.
pub(crate) used_ids: IdMap<Rect>, pub(crate) used_ids: IdMap<Rect>,
@ -20,9 +22,9 @@ pub(crate) struct FrameState {
/// How much space is used by panels. /// How much space is used by panels.
pub(crate) used_by_panels: Rect, pub(crate) used_by_panels: Rect,
/// If a tooltip has been shown this frame, where was it? /// If a tooltip has been shown this pass, where was it?
/// This is used to prevent multiple tooltips to cover each other. /// This is used to prevent multiple tooltips to cover each other.
/// Initialized to `None` at the start of each frame. /// Initialized to `None` at the start of each pass.
pub(crate) tooltip_rect: Option<(Id, Rect, usize)>, pub(crate) tooltip_rect: Option<(Id, Rect, usize)>,
/// Cleared by the first `ScrollArea` that makes use of it. /// Cleared by the first `ScrollArea` that makes use of it.
@ -46,7 +48,7 @@ impl Default for FrameState {
} }
impl FrameState { impl FrameState {
pub(crate) fn begin_frame(&mut self, input: &InputState) { pub(crate) fn begin_pass(&mut self, input: &InputState) {
let Self { let Self {
used_ids, used_ids,
available_rect, available_rect,

View file

@ -93,55 +93,59 @@ impl Default for InputState {
} }
impl InputState { impl InputState {
#[must_use] /// Only update the basics (time, screen_rect, dt, …)
pub fn begin_frame(mut self, new: RawInput) -> InputState { pub(crate) fn begin_frame(&mut self, new: &RawInput) {
let time = new let old_time = self.time;
let new_time = new
.time .time
.unwrap_or_else(|| self.time + new.predicted_dt as f64); .unwrap_or_else(|| self.time + new.predicted_dt as f64);
let unstable_dt = (time - self.time) as f32;
let screen_rect = new.screen_rect.unwrap_or(self.screen_rect);
self.create_touch_states_for_new_devices(&new.events);
for touch_state in self.touch_states.values_mut() {
touch_state.begin_frame(time, &new, self.pointer.interact_pos);
}
let pointer = self.pointer.begin_frame(time, &new);
let mut keys_down = self.keys_down; self.pixels_per_point = new.pixels_per_point.unwrap_or(self.pixels_per_point);
let mut scroll_delta = Vec2::ZERO; self.screen_rect = new.screen_rect.unwrap_or(self.screen_rect);
let mut zoom_factor_delta = 1.0; self.time = new_time;
self.pointer.time = new_time;
self.pointer.delta = Vec2::ZERO;
self.pointer.pointer_events.clear();
self.unstable_dt = (new_time - old_time) as f32;
self.predicted_dt = new.predicted_dt;
self.modifiers = new.modifiers;
self.scroll_delta = Vec2::ZERO;
self.zoom_factor_delta = 1.0;
self.events.clear();
}
/// Take all events
pub(crate) fn on_events(&mut self, new: RawInput) {
self.create_touch_states_for_new_devices(&new.events);
self.pointer.on_events(&new.events);
for touch_state in self.touch_states.values_mut() {
touch_state.on_events(self.time, &new.events, self.pointer.interact_pos);
}
self.scroll_delta = Vec2::ZERO;
self.zoom_factor_delta = 1.0;
for event in &new.events { for event in &new.events {
match event { match event {
Event::Key { key, pressed, .. } => { Event::Key { key, pressed, .. } => {
if *pressed { if *pressed {
keys_down.insert(*key); self.keys_down.insert(*key);
} else { } else {
keys_down.remove(key); self.keys_down.remove(key);
} }
} }
Event::Scroll(delta) => { Event::Scroll(delta) => {
scroll_delta += *delta; self.scroll_delta += *delta;
} }
Event::Zoom(factor) => { Event::Zoom(factor) => {
zoom_factor_delta *= *factor; self.zoom_factor_delta *= *factor;
} }
_ => {} _ => {}
} }
} }
InputState {
pointer, self.events = new.events.clone(); // TODO: remove clone() and use raw.events, or remove [`Self::raw].
touch_states: self.touch_states, self.raw = new;
scroll_delta,
zoom_factor_delta,
screen_rect,
pixels_per_point: new.pixels_per_point.unwrap_or(self.pixels_per_point),
time,
unstable_dt,
predicted_dt: new.predicted_dt,
modifiers: new.modifiers,
keys_down,
events: new.events.clone(), // TODO: remove clone() and use raw.events
raw: new,
}
} }
#[inline(always)] #[inline(always)]
@ -417,16 +421,13 @@ impl Default for PointerState {
} }
impl PointerState { impl PointerState {
#[must_use] pub(crate) fn on_events(&mut self, events: &[Event]) {
pub(crate) fn begin_frame(mut self, time: f64, new: &RawInput) -> PointerState {
self.time = time;
self.pointer_events.clear(); self.pointer_events.clear();
let old_pos = self.latest_pos; let old_pos = self.latest_pos;
self.interact_pos = self.latest_pos; self.interact_pos = self.latest_pos;
for event in &new.events { for event in events {
match event { match event {
Event::PointerMoved(pos) => { Event::PointerMoved(pos) => {
let pos = *pos; let pos = *pos;
@ -463,7 +464,7 @@ impl PointerState {
if pressed { if pressed {
self.press_origin = Some(pos); self.press_origin = Some(pos);
self.press_start_time = Some(time); self.press_start_time = Some(self.time);
self.has_moved_too_much_for_a_click = false; self.has_moved_too_much_for_a_click = false;
self.pointer_events.push(PointerEvent::Pressed(pos)); self.pointer_events.push(PointerEvent::Pressed(pos));
} else { } else {
@ -471,10 +472,10 @@ impl PointerState {
let click = if clicked { let click = if clicked {
let double_click = let double_click =
(time - self.last_click_time) < MAX_DOUBLE_CLICK_DELAY; (self.time - self.last_click_time) < MAX_DOUBLE_CLICK_DELAY;
let count = if double_click { 2 } else { 1 }; let count = if double_click { 2 } else { 1 };
self.last_click_time = time; self.last_click_time = self.time;
Some(Click { Some(Click {
pos, pos,
@ -509,22 +510,20 @@ impl PointerState {
}; };
if let Some(pos) = self.latest_pos { if let Some(pos) = self.latest_pos {
self.pos_history.add(time, pos); self.pos_history.add(self.time, pos);
} else { } else {
// we do not clear the `pos_history` here, because it is exactly when a finger has // we do not clear the `pos_history` here, because it is exactly when a finger has
// released from the touch screen that we may want to assign a velocity to whatever // released from the touch screen that we may want to assign a velocity to whatever
// the user tried to throw. // the user tried to throw.
} }
self.pos_history.flush(time); self.pos_history.flush(self.time);
self.velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 { self.velocity = if self.pos_history.len() >= 3 && self.pos_history.duration() > 0.01 {
self.pos_history.velocity().unwrap_or_default() self.pos_history.velocity().unwrap_or_default()
} else { } else {
Vec2::default() Vec2::default()
}; };
self
} }
fn wants_repaint(&self) -> bool { fn wants_repaint(&self) -> bool {

View file

@ -3,7 +3,7 @@ use std::{collections::BTreeMap, fmt::Debug};
use crate::{ use crate::{
data::input::TouchDeviceId, data::input::TouchDeviceId,
emath::{normalized_angle, Pos2, Vec2}, emath::{normalized_angle, Pos2, Vec2},
Event, RawInput, TouchId, TouchPhase, Event, TouchId, TouchPhase,
}; };
/// All you probably need to know about a multi-touch gesture. /// All you probably need to know about a multi-touch gesture.
@ -122,9 +122,9 @@ impl TouchState {
} }
} }
pub fn begin_frame(&mut self, time: f64, new: &RawInput, pointer_pos: Option<Pos2>) { pub(crate) fn on_events(&mut self, time: f64, events: &[Event], pointer_pos: Option<Pos2>) {
let mut added_or_removed_touches = false; let mut added_or_removed_touches = false;
for event in &new.events { for event in events {
match *event { match *event {
Event::Touch { Event::Touch {
device_id, device_id,

View file

@ -230,6 +230,18 @@ impl Layout {
} }
} }
/// change layout to one suitable for size pass
pub(crate) fn for_size_pass(self) -> Self {
Self {
main_dir: self.main_dir,
main_wrap: self.main_wrap,
main_align: Align::Min,
main_justify: false,
cross_align: Align::Min,
cross_justify: false,
}
}
#[inline(always)] #[inline(always)]
pub fn with_main_wrap(self, main_wrap: bool) -> Self { pub fn with_main_wrap(self, main_wrap: bool) -> Self {
Self { main_wrap, ..self } Self { main_wrap, ..self }

View file

@ -396,6 +396,8 @@ pub mod text {
}; };
} }
pub(crate) use context::PassState;
pub use { pub use {
containers::*, containers::*,
context::{Context, CtxRef}, context::{Context, CtxRef},

View file

@ -100,6 +100,11 @@ pub struct Options {
/// but is a signal to any backend that we want the [`crate::Output::events`] read out loud. /// but is a signal to any backend that we want the [`crate::Output::events`] read out loud.
/// Screen readers is an experimental feature of egui, and not supported on all platforms. /// Screen readers is an experimental feature of egui, and not supported on all platforms.
pub screen_reader: bool, pub screen_reader: bool,
/// Enable experimental two-pass layout.
///
/// See https://github.com/emilk/egui/issues/843
pub multi_pass: bool,
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View file

@ -92,10 +92,15 @@ impl Ui {
pub fn child_ui_with_id_source( pub fn child_ui_with_id_source(
&mut self, &mut self,
max_rect: Rect, max_rect: Rect,
layout: Layout, mut layout: Layout,
id_source: impl Hash, id_source: impl Hash,
) -> Self { ) -> Self {
crate::egui_assert!(!max_rect.any_nan()); crate::egui_assert!(!max_rect.any_nan());
if self.ctx().pass_state == Some(PassState::SizePass) {
layout = layout.for_size_pass();
}
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value(); let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1); self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
let menu_state = self.get_menu_state(); let menu_state = self.get_menu_state();

View file

@ -132,6 +132,13 @@ impl BackendPanel {
ui.ctx().memory().options.screen_reader = screen_reader; ui.ctx().memory().options.screen_reader = screen_reader;
} }
{
let mut multi_pass = ui.ctx().memory().options.multi_pass;
ui.checkbox(&mut multi_pass, "☯ Multipass (experimental)")
.on_hover_text("Experimental two-pass layout");
ui.ctx().memory().options.multi_pass = multi_pass;
}
if !frame.is_web() { if !frame.is_web() {
ui.separator(); ui.separator();
if ui.button("Quit").clicked() { if ui.button("Quit").clicked() {

View file

@ -398,8 +398,8 @@ impl Fonts {
} }
/// Must be called once per frame to clear the [`Galley`] cache. /// Must be called once per frame to clear the [`Galley`] cache.
pub fn end_frame(&self) { pub fn prune_cache(&self) {
self.galley_cache.lock().end_frame(); self.galley_cache.lock().prune();
} }
} }
@ -453,8 +453,8 @@ impl GalleyCache {
self.cache.len() self.cache.len()
} }
/// Must be called once per frame to clear the [`Galley`] cache. /// Must be called once per frame to prune the [`Galley`] cache.
pub fn end_frame(&mut self) { pub fn prune(&mut self) {
let current_generation = self.generation; let current_generation = self.generation;
self.cache.retain(|_key, cached| { self.cache.retain(|_key, cached| {
cached.last_used == current_generation // only keep those that were used this frame cached.last_used == current_generation // only keep those that were used this frame