mirror of
https://github.com/danbulant/cushy
synced 2026-09-18 05:53:46 +00:00
Window focus/occlusion, recursive focus/activation
This commit is contained in:
parent
ca42ecc956
commit
57a689b8c8
5 changed files with 152 additions and 29 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
11
src/value.rs
11
src/value.rs
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)`.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue