Window focus/occlusion, recursive focus/activation

This commit is contained in:
Jonathan Johnson 2023-11-07 20:16:59 -08:00
parent ca42ecc956
commit 57a689b8c8
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
5 changed files with 152 additions and 29 deletions

18
Cargo.lock generated
View file

@ -427,9 +427,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.5" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.48.0", "windows-sys 0.48.0",
@ -579,9 +579,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@ -853,7 +853,7 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]] [[package]]
name = "kludgine" name = "kludgine"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/khonsulabs/kludgine#2650642bddb0cdc37b788cda6c552ded11e73b48" source = "git+https://github.com/khonsulabs/kludgine#fd2078efb7212db0f07120b80440699b00ec2a2b"
dependencies = [ dependencies = [
"ahash", "ahash",
"alot", "alot",
@ -1510,18 +1510,18 @@ checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.191" version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a834c4821019838224821468552240d4d95d14e751986442c816572d39a080c9" checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.191" version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fa52d5646bce91b680189fe5b1c049d2ea38dabb4e2e7c8d00ca12cfbfbcfd" checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -172,8 +172,21 @@ impl<'context, 'window> EventContext<'context, 'window> {
} }
pub(crate) fn apply_pending_state(&mut self) { pub(crate) fn apply_pending_state(&mut self) {
const MAX_ITERS: u8 = 100;
// These two blocks apply active/focus in a loop to pick up the event
// where during the process of calling deactivate/blur or activate/focus
// the active/focus widget is changed again. This can lead to infinite
// loops, which is a programmer error. However, rather than block
// forever, we log a message that this is happening and break.
let mut activation_changes = 0;
while activation_changes < MAX_ITERS {
let active = self.pending_state.active.clone(); let active = self.pending_state.active.clone();
if self.current_node.tree.active_widget() != active.as_ref().map(ManagedWidget::id) { if self.current_node.tree.active_widget() == active.as_ref().map(ManagedWidget::id) {
break;
}
activation_changes += 1;
let new = match self.current_node.tree.activate(active.as_ref()) { let new = match self.current_node.tree.activate(active.as_ref()) {
Ok(old) => { Ok(old) => {
if let Some(old) = old { if let Some(old) = old {
@ -185,19 +198,33 @@ impl<'context, 'window> EventContext<'context, 'window> {
Err(_) => false, Err(_) => false,
}; };
if new { if new {
if let Some(active) = &active { if let Some(active) = self.pending_state.active.clone() {
active active
.lock() .lock()
.as_widget() .as_widget()
.activate(&mut self.for_other(active)); .activate(&mut self.for_other(&active));
} }
self.pending_state.active = active; self.pending_state.active = active;
} else {
break;
} }
} }
if activation_changes == MAX_ITERS {
eprintln!(
"activation change force stopped after {activation_changes} sequential changes"
);
}
let mut focus_changes = 0;
while focus_changes < MAX_ITERS {
let focus = self.pending_state.focus.clone(); let focus = self.pending_state.focus.clone();
if self.current_node.tree.focused_widget() != focus.as_ref().map(ManagedWidget::id) { if self.current_node.tree.focused_widget() == focus.as_ref().map(ManagedWidget::id) {
let focus = focus.and_then(|mut focus| loop { break;
}
focus_changes += 1;
self.pending_state.focus = focus.and_then(|mut focus| loop {
if focus if focus
.lock() .lock()
.as_widget() .as_widget()
@ -210,7 +237,11 @@ impl<'context, 'window> EventContext<'context, 'window> {
break self.next_focus_after(focus, VisualOrder::left_to_right()); break self.next_focus_after(focus, VisualOrder::left_to_right());
} }
}); });
let new = match self.current_node.tree.focus(focus.as_ref()) { let new = match self
.current_node
.tree
.focus(self.pending_state.focus.as_ref())
{
Ok(old) => { Ok(old) => {
if let Some(old) = old { if let Some(old) = old {
let mut old_context = self.for_other(&old); let mut old_context = self.for_other(&old);
@ -221,12 +252,17 @@ impl<'context, 'window> EventContext<'context, 'window> {
Err(_) => false, Err(_) => false,
}; };
if new { if new {
if let Some(focus) = &focus { if let Some(focus) = self.pending_state.focus.clone() {
focus.lock().as_widget().focus(&mut self.for_other(focus)); focus.lock().as_widget().focus(&mut self.for_other(&focus));
} }
self.pending_state.focus = focus; } else {
break;
} }
} }
if focus_changes == MAX_ITERS {
eprintln!("focus change force stopped after {focus_changes} sequential changes");
}
} }
fn next_focus_after( fn next_focus_after(

View file

@ -54,6 +54,17 @@ impl<T> Dynamic<T> {
self.0.for_each(move |gen| for_each(&gen.value)); self.0.for_each(move |gen| for_each(&gen.value));
} }
/// Attaches `for_each` to this value so that it is invoked each time the
/// value's contents are updated. This function returns `self`.
#[must_use]
pub fn with_for_each<F>(self, mut for_each: F) -> Self
where
F: for<'a> FnMut(&'a T) + Send + 'static,
{
self.0.for_each(move |gen| for_each(&gen.value));
self
}
/// Creates a new dynamic value that contains the result of invoking `map` /// Creates a new dynamic value that contains the result of invoking `map`
/// each time this value is changed. /// each time this value is changed.
pub fn map_each<R, F>(&self, mut map: F) -> Dynamic<R> pub fn map_each<R, F>(&self, mut map: F) -> Dynamic<R>

View file

@ -172,6 +172,11 @@ pub trait MakeWidget: Sized {
/// Returns a new widget. /// Returns a new widget.
fn make_widget(self) -> WidgetInstance; fn make_widget(self) -> WidgetInstance;
/// Returns a new window containing `self` as the root widget.
fn into_window(self) -> Window<WidgetInstance> {
Window::new(self.make_widget())
}
/// Associates `styles` with this widget. /// Associates `styles` with this widget.
/// ///
/// This is equivalent to `Style::new(styles, self)`. /// This is equivalent to `Style::new(styles, self)`.

View file

@ -28,6 +28,7 @@ use crate::graphics::Graphics;
use crate::styles::components::VisualOrder; use crate::styles::components::VisualOrder;
use crate::tree::Tree; use crate::tree::Tree;
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
use crate::value::Dynamic;
use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED}; use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED};
use crate::window::sealed::WindowCommand; use crate::window::sealed::WindowCommand;
use crate::{ConstraintLimit, Run}; use crate::{ConstraintLimit, Run};
@ -46,7 +47,9 @@ where
{ {
context: Behavior::Context, context: Behavior::Context,
/// The attributes of this window. /// The attributes of this window.
pub attributes: WindowAttributes, attributes: WindowAttributes,
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
} }
impl<Behavior> Default for Window<Behavior> impl<Behavior> Default for Window<Behavior>
@ -68,6 +71,34 @@ impl Window<WidgetInstance> {
{ {
Self::new(WidgetInstance::new(widget)) Self::new(WidgetInstance::new(widget))
} }
/// Sets `focused` to be the dynamic updated when this window's focus status
/// is changed.
///
/// When the window is focused for user input, the dynamic will contain
/// `true`.
///
/// `focused` will be initialized with an initial state
/// of `false`.
pub fn with_focused(mut self, focused: Dynamic<bool>) -> Self {
focused.update(false);
self.focused = Some(focused);
self
}
/// Sets `occluded` to be the dynamic updated when this window's occlusion
/// status is changed.
///
/// When the window is occluded (completely hidden/offscreen/minimized), the
/// dynamic will contain `true`. If the window is at least partially
/// visible, this value will contain `true`.
///
/// `occluded` will be initialized with an initial state of `false`.
pub fn with_occluded(mut self, occluded: Dynamic<bool>) -> Self {
occluded.update(false);
self.occluded = Some(occluded);
self
}
} }
impl<Behavior> Window<Behavior> impl<Behavior> Window<Behavior>
@ -98,6 +129,8 @@ where
..WindowAttributes::default() ..WindowAttributes::default()
}, },
context, context,
occluded: None,
focused: None,
} }
} }
} }
@ -107,12 +140,14 @@ where
Behavior: WindowBehavior, Behavior: WindowBehavior,
{ {
fn run(self) -> crate::Result { fn run(self) -> crate::Result {
GooeyWindow::<Behavior>::run_with(AssertUnwindSafe(( GooeyWindow::<Behavior>::run_with(AssertUnwindSafe(sealed::Context {
self.context, user: self.context,
RefCell::new(sealed::WindowSettings { settings: RefCell::new(sealed::WindowSettings {
attributes: Some(self.attributes), attributes: Some(self.attributes),
occluded: self.occluded,
focused: self.focused,
}), }),
))) }))
} }
} }
@ -157,6 +192,8 @@ struct GooeyWindow<T> {
mouse_state: MouseState, mouse_state: MouseState,
redraw_status: RedrawStatus, redraw_status: RedrawStatus,
initial_frame: bool, initial_frame: bool,
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
} }
impl<T> GooeyWindow<T> impl<T> GooeyWindow<T>
@ -174,15 +211,17 @@ impl<T> kludgine::app::WindowBehavior<WindowCommand> for GooeyWindow<T>
where where
T: WindowBehavior, T: WindowBehavior,
{ {
type Context = AssertUnwindSafe<(T::Context, RefCell<sealed::WindowSettings>)>; type Context = AssertUnwindSafe<sealed::Context<T::Context>>;
fn initialize( fn initialize(
mut window: RunningWindow<'_>, mut window: RunningWindow<'_>,
_graphics: &mut kludgine::Graphics<'_>, _graphics: &mut kludgine::Graphics<'_>,
context: Self::Context, AssertUnwindSafe(context): Self::Context,
) -> Self { ) -> Self {
let mut behavior = T::initialize(&mut window, context.0 .0); let mut behavior = T::initialize(&mut window, context.user);
let root = Tree::default().push_boxed(behavior.make_root(), None); let root = Tree::default().push_boxed(behavior.make_root(), None);
let occluded = context.settings.borrow_mut().occluded.take();
let focused = context.settings.borrow_mut().focused.take();
Self { Self {
behavior, behavior,
@ -196,6 +235,8 @@ where
}, },
redraw_status: RedrawStatus::default(), redraw_status: RedrawStatus::default(),
initial_frame: true, initial_frame: true,
occluded,
focused,
} }
} }
@ -232,6 +273,26 @@ where
} }
} }
fn focus_changed(
&mut self,
window: kludgine::app::Window<'_, WindowCommand>,
_kludgine: &mut Kludgine,
) {
if let Some(focused) = &self.focused {
focused.update(window.focused());
}
}
fn occlusion_changed(
&mut self,
window: kludgine::app::Window<'_, WindowCommand>,
_kludgine: &mut Kludgine,
) {
if let Some(occluded) = &self.occluded {
occluded.update(window.ocluded());
}
}
fn render<'pass>( fn render<'pass>(
&'pass mut self, &'pass mut self,
_window: RunningWindow<'_>, _window: RunningWindow<'_>,
@ -246,7 +307,7 @@ where
context: &Self::Context, context: &Self::Context,
) -> kludgine::app::WindowAttributes<WindowCommand> { ) -> kludgine::app::WindowAttributes<WindowCommand> {
context context
.1 .settings
.borrow_mut() .borrow_mut()
.attributes .attributes
.take() .take()
@ -558,10 +619,20 @@ struct MouseState {
} }
pub(crate) mod sealed { pub(crate) mod sealed {
use std::cell::RefCell;
use crate::value::Dynamic;
use crate::window::WindowAttributes; use crate::window::WindowAttributes;
pub struct Context<C> {
pub user: C,
pub settings: RefCell<WindowSettings>,
}
pub struct WindowSettings { pub struct WindowSettings {
pub attributes: Option<WindowAttributes>, pub attributes: Option<WindowAttributes>,
pub occluded: Option<Dynamic<bool>>,
pub focused: Option<Dynamic<bool>>,
} }
pub enum WindowCommand { pub enum WindowCommand {