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

View file

@ -172,8 +172,21 @@ impl<'context, 'window> EventContext<'context, 'window> {
}
pub(crate) fn apply_pending_state(&mut self) {
let active = self.pending_state.active.clone();
if self.current_node.tree.active_widget() != active.as_ref().map(ManagedWidget::id) {
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();
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()) {
Ok(old) => {
if let Some(old) = old {
@ -185,19 +198,33 @@ impl<'context, 'window> EventContext<'context, 'window> {
Err(_) => false,
};
if new {
if let Some(active) = &active {
if let Some(active) = self.pending_state.active.clone() {
active
.lock()
.as_widget()
.activate(&mut self.for_other(active));
.activate(&mut self.for_other(&active));
}
self.pending_state.active = active;
} else {
break;
}
}
let focus = self.pending_state.focus.clone();
if self.current_node.tree.focused_widget() != focus.as_ref().map(ManagedWidget::id) {
let focus = focus.and_then(|mut focus| loop {
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();
if self.current_node.tree.focused_widget() == focus.as_ref().map(ManagedWidget::id) {
break;
}
focus_changes += 1;
self.pending_state.focus = focus.and_then(|mut focus| loop {
if focus
.lock()
.as_widget()
@ -210,7 +237,11 @@ impl<'context, 'window> EventContext<'context, 'window> {
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) => {
if let Some(old) = old {
let mut old_context = self.for_other(&old);
@ -221,12 +252,17 @@ impl<'context, 'window> EventContext<'context, 'window> {
Err(_) => false,
};
if new {
if let Some(focus) = &focus {
focus.lock().as_widget().focus(&mut self.for_other(focus));
if let Some(focus) = self.pending_state.focus.clone() {
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(

View file

@ -54,6 +54,17 @@ impl<T> Dynamic<T> {
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`
/// each time this value is changed.
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.
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.
///
/// 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::tree::Tree;
use crate::utils::ModifiersExt;
use crate::value::Dynamic;
use crate::widget::{EventHandling, ManagedWidget, Widget, WidgetInstance, HANDLED, IGNORED};
use crate::window::sealed::WindowCommand;
use crate::{ConstraintLimit, Run};
@ -46,7 +47,9 @@ where
{
context: Behavior::Context,
/// The attributes of this window.
pub attributes: WindowAttributes,
attributes: WindowAttributes,
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
}
impl<Behavior> Default for Window<Behavior>
@ -68,6 +71,34 @@ impl Window<WidgetInstance> {
{
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>
@ -98,6 +129,8 @@ where
..WindowAttributes::default()
},
context,
occluded: None,
focused: None,
}
}
}
@ -107,12 +140,14 @@ where
Behavior: WindowBehavior,
{
fn run(self) -> crate::Result {
GooeyWindow::<Behavior>::run_with(AssertUnwindSafe((
self.context,
RefCell::new(sealed::WindowSettings {
GooeyWindow::<Behavior>::run_with(AssertUnwindSafe(sealed::Context {
user: self.context,
settings: RefCell::new(sealed::WindowSettings {
attributes: Some(self.attributes),
occluded: self.occluded,
focused: self.focused,
}),
)))
}))
}
}
@ -157,6 +192,8 @@ struct GooeyWindow<T> {
mouse_state: MouseState,
redraw_status: RedrawStatus,
initial_frame: bool,
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
}
impl<T> GooeyWindow<T>
@ -174,15 +211,17 @@ impl<T> kludgine::app::WindowBehavior<WindowCommand> for GooeyWindow<T>
where
T: WindowBehavior,
{
type Context = AssertUnwindSafe<(T::Context, RefCell<sealed::WindowSettings>)>;
type Context = AssertUnwindSafe<sealed::Context<T::Context>>;
fn initialize(
mut window: RunningWindow<'_>,
_graphics: &mut kludgine::Graphics<'_>,
context: Self::Context,
AssertUnwindSafe(context): Self::Context,
) -> 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 occluded = context.settings.borrow_mut().occluded.take();
let focused = context.settings.borrow_mut().focused.take();
Self {
behavior,
@ -196,6 +235,8 @@ where
},
redraw_status: RedrawStatus::default(),
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>(
&'pass mut self,
_window: RunningWindow<'_>,
@ -246,7 +307,7 @@ where
context: &Self::Context,
) -> kludgine::app::WindowAttributes<WindowCommand> {
context
.1
.settings
.borrow_mut()
.attributes
.take()
@ -558,10 +619,20 @@ struct MouseState {
}
pub(crate) mod sealed {
use std::cell::RefCell;
use crate::value::Dynamic;
use crate::window::WindowAttributes;
pub struct Context<C> {
pub user: C,
pub settings: RefCell<WindowSettings>,
}
pub struct WindowSettings {
pub attributes: Option<WindowAttributes>,
pub occluded: Option<Dynamic<bool>>,
pub focused: Option<Dynamic<bool>>,
}
pub enum WindowCommand {