Next-focus progress

gameui no longer needs next_focus for the input to be focused
automatically
This commit is contained in:
Jonathan Johnson 2023-11-07 08:39:35 -08:00
parent e7b4fe00b6
commit 8a2dae3f76
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
4 changed files with 215 additions and 49 deletions

View file

@ -1,7 +1,7 @@
use gooey::children;
use gooey::value::Dynamic; use gooey::value::Dynamic;
use gooey::widget::{MakeWidget, HANDLED, IGNORED}; use gooey::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::{Canvas, Expand, Input, Label, Scroll, Stack}; use gooey::widgets::{Canvas, Expand, Input, Label, Scroll, Stack};
use gooey::{children, Run};
use kludgine::app::winit::event::ElementState; use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::Key; use kludgine::app::winit::keyboard::Key;
use kludgine::figures::{Point, Rect}; use kludgine::figures::{Point, Rect};
@ -12,27 +12,9 @@ fn main() -> gooey::Result {
let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100)); let chat_log = Dynamic::new("Chat log goes here.\n".repeat(100));
let chat_message = Dynamic::new(String::new()); let chat_message = Dynamic::new(String::new());
let input = Input::new(chat_message.clone())
.on_key({
let chat_log = chat_log.clone();
move |input| match (input.state, input.logical_key) {
(ElementState::Pressed, Key::Enter) => {
let new_message = chat_message.map_mut(|text| std::mem::take(text));
chat_log.map_mut(|chat_log| {
chat_log.push_str(&new_message);
chat_log.push('\n');
});
HANDLED
}
_ => IGNORED,
}
})
.make_widget();
let input_id = input.id();
Expand::new(Stack::rows(children![ Expand::new(Stack::rows(children![
Expand::new(Stack::columns(children![ Expand::new(Stack::columns(children![
Expand::new(Scroll::vertical(Label::new(chat_log))), Expand::new(Scroll::vertical(Label::new(chat_log.clone()))),
Expand::weighted( Expand::weighted(
2, 2,
Canvas::new(|context| { Canvas::new(|context| {
@ -46,8 +28,19 @@ fn main() -> gooey::Result {
}) })
) )
])), ])),
input.clone(), Input::new(chat_message.clone())
.on_key(move |input| match (input.state, input.logical_key) {
(ElementState::Pressed, Key::Enter) => {
let new_message = chat_message.map_mut(|text| std::mem::take(text));
chat_log.map_mut(|chat_log| {
chat_log.push_str(&new_message);
chat_log.push('\n');
});
HANDLED
}
_ => IGNORED,
})
.make_widget(),
])) ]))
.with_next_focus(input_id)
.run() .run()
} }

View file

@ -15,7 +15,7 @@ use crate::graphics::Graphics;
use crate::styles::components::HighlightColor; use crate::styles::components::HighlightColor;
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles}; use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
use crate::value::Dynamic; use crate::value::Dynamic;
use crate::widget::{EventHandling, ManagedWidget, WidgetInstance}; use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance};
use crate::window::{sealed, RunningWindow}; use crate::window::{sealed, RunningWindow};
use crate::ConstraintLimit; use crate::ConstraintLimit;
@ -200,8 +200,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
} else if let Some(next_focus) = focus.next_focus() { } else if let Some(next_focus) = focus.next_focus() {
focus = next_focus; focus = next_focus;
} else { } else {
// TODO visually scan the tree for the "next" widget. break self.next_focus_after(focus);
break None;
} }
}); });
let new = match self.current_node.tree.focus(focus.as_ref()) { let new = match self.current_node.tree.focus(focus.as_ref()) {
@ -225,6 +224,79 @@ impl<'context, 'window> EventContext<'context, 'window> {
} }
} }
} }
fn next_focus_after(&mut self, mut focus: ManagedWidget) -> Option<ManagedWidget> {
// First, look within the current focus for any focusable children.
let stop_at = focus.id();
if let Some(focus) = self.next_focus_within(&focus, None, stop_at) {
return Some(focus);
}
// Now, look for the next widget in each hierarchy
let root = loop {
if let Some(focus) = self.next_focus_sibling(&focus, stop_at) {
return Some(focus);
}
let Some(parent) = focus.parent() else {
break focus;
};
focus = parent;
};
// We've exhausted a forward scan, we can now start searching the final
// parent, which is the root.
self.next_focus_within(&root, None, stop_at)
}
fn next_focus_sibling(
&mut self,
focus: &ManagedWidget,
stop_at: WidgetId,
) -> Option<ManagedWidget> {
self.next_focus_within(&focus.parent()?, Some(focus.id()), stop_at)
}
/// Searches for the next focus inside of `focus`, returning `None` if
/// `stop_at` is reached or all children are checked before finding a widget
/// that returns true from `accept_focus`.
fn next_focus_within(
&mut self,
focus: &ManagedWidget,
start_at: Option<WidgetId>,
stop_at: WidgetId,
) -> Option<ManagedWidget> {
let child_layouts = focus.child_layouts();
// TODO visually sort the layouts
let mut child_layouts = child_layouts.into_iter().peekable();
if let Some(start_at) = start_at {
// Skip all children up to `start_at`
while child_layouts.peek()?.0.id() != start_at {
child_layouts.next();
}
// Skip `start_at`
child_layouts.next();
}
for (child, _layout) in child_layouts {
// Ensure we haven't cycled completely.
if stop_at == child.id() {
break;
}
if child
.lock()
.as_widget()
.accept_focus(&mut self.for_other(child.clone()))
{
return Some(child);
} else if let Some(focus) = self.next_focus_within(&child, None, stop_at) {
return Some(focus);
}
}
None
}
} }
impl<'context, 'window> Deref for EventContext<'context, 'window> { impl<'context, 'window> Deref for EventContext<'context, 'window> {

View file

@ -1,14 +1,12 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug;
use std::mem; use std::mem;
use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Mutex, PoisonError}; use std::sync::{Arc, Mutex, PoisonError};
use kludgine::figures::units::Px; use kludgine::figures::units::Px;
use kludgine::figures::{Point, Rect}; use kludgine::figures::{Point, Rect};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles}; use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
use crate::widget::{ManagedWidget, WidgetInstance}; use crate::widget::{ManagedWidget, WidgetId, WidgetInstance};
#[derive(Clone, Default)] #[derive(Clone, Default)]
pub struct Tree { pub struct Tree {
@ -37,6 +35,9 @@ impl Tree {
let parent = data.nodes.get_mut(&parent.id()).expect("missing parent"); let parent = data.nodes.get_mut(&parent.id()).expect("missing parent");
parent.children.push(id); parent.children.push(id);
} }
if let Some(next_focus) = widget.next_focus() {
data.previous_focuses.insert(next_focus, id);
}
ManagedWidget { ManagedWidget {
widget, widget,
tree: self.clone(), tree: self.clone(),
@ -85,6 +86,19 @@ impl Tree {
} }
} }
pub(crate) fn child_layouts(&self, parent: WidgetId) -> Vec<(ManagedWidget, Rect<Px>)> {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes[&parent]
.children
.iter()
.filter_map(|id| {
data.nodes[id]
.layout
.map(|layout| (data.widget(*id, self).expect("child still owned"), layout))
})
.collect()
}
pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults { pub(crate) fn hover(&self, new_hover: Option<&ManagedWidget>) -> HoverResults {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let hovered = new_hover let hovered = new_hover
@ -220,6 +234,7 @@ struct TreeData {
focus: Option<WidgetId>, focus: Option<WidgetId>,
hover: Option<WidgetId>, hover: Option<WidgetId>,
render_order: Vec<WidgetId>, render_order: Vec<WidgetId>,
previous_focuses: HashMap<WidgetId, WidgetId>,
} }
impl TreeData { impl TreeData {
@ -242,6 +257,10 @@ impl TreeData {
parent.children.remove(index); parent.children.remove(index);
let mut detached_nodes = removed_node.children; let mut detached_nodes = removed_node.children;
if let Some(next_focus) = removed_node.widget.next_focus() {
self.previous_focuses.remove(&next_focus);
}
while let Some(node) = detached_nodes.pop() { while let Some(node) = detached_nodes.pop() {
let mut node = self.nodes.remove(&node).expect("detached node missing"); let mut node = self.nodes.remove(&node).expect("detached node missing");
detached_nodes.append(&mut node.children); detached_nodes.append(&mut node.children);
@ -339,13 +358,3 @@ pub struct Node {
pub layout: Option<Rect<Px>>, pub layout: Option<Rect<Px>>,
pub styles: Option<Styles>, pub styles: Option<Styles>,
} }
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub struct WidgetId(u64);
impl WidgetId {
pub fn unique() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
Self(COUNTER.fetch_add(1, atomic::Ordering::Acquire))
}
}

View file

@ -5,6 +5,7 @@ use std::clone::Clone;
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::{ControlFlow, Deref}; use std::ops::{ControlFlow, Deref};
use std::panic::UnwindSafe; use std::panic::UnwindSafe;
use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError}; use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use kludgine::app::winit::event::{ use kludgine::app::winit::event::{
@ -15,7 +16,7 @@ use kludgine::figures::{Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::Styles; use crate::styles::Styles;
use crate::tree::{Tree, WidgetId}; use crate::tree::Tree;
use crate::value::{IntoValue, Value}; use crate::value::{IntoValue, Value};
use crate::widgets::Style; use crate::widgets::Style;
use crate::window::{RunningWindow, Window, WindowBehavior}; use crate::window::{RunningWindow, Window, WindowBehavior};
@ -165,7 +166,7 @@ where
} }
} }
/// A type that can create a widget. /// A type that can create a [`WidgetInstance`].
pub trait MakeWidget: Sized { pub trait MakeWidget: Sized {
/// Returns a new widget. /// Returns a new widget.
fn make_widget(self) -> WidgetInstance; fn make_widget(self) -> WidgetInstance;
@ -189,12 +190,28 @@ pub trait MakeWidget: Sized {
} }
} }
impl<T> MakeWidget for T /// A type that can create a [`WidgetInstance`] with a preallocated
/// [`WidgetId`].
pub trait MakeWidgetWithId: Sized {
/// Returns a new [`WidgetInstance`] whose [`WidgetId`] is `id`.
fn make_with_id(self, id: PendingWidgetId) -> WidgetInstance;
}
impl<T> MakeWidgetWithId for T
where where
T: Widget, T: Widget,
{
fn make_with_id(self, id: PendingWidgetId) -> WidgetInstance {
WidgetInstance::with_id(self, id)
}
}
impl<T> MakeWidget for T
where
T: MakeWidgetWithId,
{ {
fn make_widget(self) -> WidgetInstance { fn make_widget(self) -> WidgetInstance {
WidgetInstance::new(self) self.make_with_id(PendingWidgetId::unique())
} }
} }
@ -248,16 +265,25 @@ pub struct WidgetInstance {
} }
impl WidgetInstance { impl WidgetInstance {
/// Returns a new instance containing `widget` that is assigned the unique
/// `id` provided.
pub fn with_id<W>(widget: W, id: PendingWidgetId) -> Self
where
W: Widget,
{
Self {
id: id.into(),
widget: Arc::new(Mutex::new(widget)),
next_focus: Value::default(),
}
}
/// Returns a new instance containing `widget`. /// Returns a new instance containing `widget`.
pub fn new<W>(widget: W) -> Self pub fn new<W>(widget: W) -> Self
where where
W: Widget, W: Widget,
{ {
Self { Self::with_id(widget, PendingWidgetId::unique())
id: WidgetId::unique(),
widget: Arc::new(Mutex::new(widget)),
next_focus: Value::default(),
}
} }
/// Returns the unique id of this widget instance. /// Returns the unique id of this widget instance.
@ -294,6 +320,15 @@ impl WidgetInstance {
pub fn run(self) -> crate::Result { pub fn run(self) -> crate::Result {
Window::<WidgetInstance>::new(self).run() Window::<WidgetInstance>::new(self).run()
} }
/// Returns the id of the widget that should receive focus after this
/// widget.
///
/// This value comes from [`MakeWidget::with_next_focus()`].
#[must_use]
pub fn next_focus(&self) -> Option<WidgetId> {
self.next_focus.get()
}
} }
impl Eq for WidgetInstance {} impl Eq for WidgetInstance {}
@ -399,8 +434,7 @@ impl ManagedWidget {
#[must_use] #[must_use]
pub fn next_focus(&self) -> Option<ManagedWidget> { pub fn next_focus(&self) -> Option<ManagedWidget> {
self.widget self.widget
.next_focus .next_focus()
.get()
.and_then(|next_focus| self.tree.widget(next_focus)) .and_then(|next_focus| self.tree.widget(next_focus))
} }
@ -449,6 +483,10 @@ impl ManagedWidget {
pub(crate) fn reset_child_layouts(&self) { pub(crate) fn reset_child_layouts(&self) {
self.tree.reset_child_layouts(self.id()); self.tree.reset_child_layouts(self.id());
} }
pub(crate) fn child_layouts(&self) -> Vec<(ManagedWidget, Rect<Px>)> {
self.tree.child_layouts(self.id())
}
} }
impl PartialEq for ManagedWidget { impl PartialEq for ManagedWidget {
@ -592,3 +630,57 @@ impl WidgetRef {
widget.clone() widget.clone()
} }
} }
/// The unique id of a [`WidgetInstance`].
///
/// Each [`WidgetInstance`] is guaranteed to have a unique [`WidgetId`] across
/// the lifetime of an application.
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub struct WidgetId(u64);
impl WidgetId {
fn unique() -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(0);
Self(COUNTER.fetch_add(1, atomic::Ordering::Acquire))
}
}
/// A [`WidgetId`] that has not been assigned to a [`WidgetInstance`].
///
/// This type is passed to [`MakeWidgetWithId::make_with_id()`] to create a
/// [`WidgetInstance`] with a preallocated id.
///
/// This type cannot be cloned or copied to ensure only a single widget can be
/// assigned a given [`WidgetId`]. The contained [`WidgetId`] can be accessed
/// via [`id()`](Self::id), `Into<WidgetId>`, or `Deref`.
#[derive(Eq, PartialEq, Debug)]
pub struct PendingWidgetId(WidgetId);
impl PendingWidgetId {
/// Returns a newly allocated [`WidgetId`] that is guaranteed to be unique
/// for the lifetime of the application.
#[must_use]
pub fn unique() -> Self {
Self(WidgetId::unique())
}
/// Returns the contained widget id.
#[must_use]
pub const fn id(&self) -> WidgetId {
self.0
}
}
impl From<PendingWidgetId> for WidgetId {
fn from(value: PendingWidgetId) -> Self {
value.0
}
}
impl Deref for PendingWidgetId {
type Target = WidgetId;
fn deref(&self) -> &Self::Target {
&self.0
}
}