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::widget::{MakeWidget, HANDLED, IGNORED};
use gooey::widgets::{Canvas, Expand, Input, Label, Scroll, Stack};
use gooey::{children, Run};
use kludgine::app::winit::event::ElementState;
use kludgine::app::winit::keyboard::Key;
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_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::columns(children![
Expand::new(Scroll::vertical(Label::new(chat_log))),
Expand::new(Scroll::vertical(Label::new(chat_log.clone()))),
Expand::weighted(
2,
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()
}

View file

@ -15,7 +15,7 @@ use crate::graphics::Graphics;
use crate::styles::components::HighlightColor;
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles};
use crate::value::Dynamic;
use crate::widget::{EventHandling, ManagedWidget, WidgetInstance};
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance};
use crate::window::{sealed, RunningWindow};
use crate::ConstraintLimit;
@ -200,8 +200,7 @@ impl<'context, 'window> EventContext<'context, 'window> {
} else if let Some(next_focus) = focus.next_focus() {
focus = next_focus;
} else {
// TODO visually scan the tree for the "next" widget.
break None;
break self.next_focus_after(focus);
}
});
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> {

View file

@ -1,14 +1,12 @@
use std::collections::HashMap;
use std::fmt::Debug;
use std::mem;
use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Mutex, PoisonError};
use kludgine::figures::units::Px;
use kludgine::figures::{Point, Rect};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
use crate::widget::{ManagedWidget, WidgetInstance};
use crate::widget::{ManagedWidget, WidgetId, WidgetInstance};
#[derive(Clone, Default)]
pub struct Tree {
@ -37,6 +35,9 @@ impl Tree {
let parent = data.nodes.get_mut(&parent.id()).expect("missing parent");
parent.children.push(id);
}
if let Some(next_focus) = widget.next_focus() {
data.previous_focuses.insert(next_focus, id);
}
ManagedWidget {
widget,
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 {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
let hovered = new_hover
@ -220,6 +234,7 @@ struct TreeData {
focus: Option<WidgetId>,
hover: Option<WidgetId>,
render_order: Vec<WidgetId>,
previous_focuses: HashMap<WidgetId, WidgetId>,
}
impl TreeData {
@ -242,6 +257,10 @@ impl TreeData {
parent.children.remove(index);
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() {
let mut node = self.nodes.remove(&node).expect("detached node missing");
detached_nodes.append(&mut node.children);
@ -339,13 +358,3 @@ pub struct Node {
pub layout: Option<Rect<Px>>,
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::ops::{ControlFlow, Deref};
use std::panic::UnwindSafe;
use std::sync::atomic::{self, AtomicU64};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use kludgine::app::winit::event::{
@ -15,7 +16,7 @@ use kludgine::figures::{Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::Styles;
use crate::tree::{Tree, WidgetId};
use crate::tree::Tree;
use crate::value::{IntoValue, Value};
use crate::widgets::Style;
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 {
/// Returns a new widget.
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
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 {
WidgetInstance::new(self)
self.make_with_id(PendingWidgetId::unique())
}
}
@ -248,16 +265,25 @@ pub struct 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`.
pub fn new<W>(widget: W) -> Self
where
W: Widget,
{
Self {
id: WidgetId::unique(),
widget: Arc::new(Mutex::new(widget)),
next_focus: Value::default(),
}
Self::with_id(widget, PendingWidgetId::unique())
}
/// Returns the unique id of this widget instance.
@ -294,6 +320,15 @@ impl WidgetInstance {
pub fn run(self) -> crate::Result {
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 {}
@ -399,8 +434,7 @@ impl ManagedWidget {
#[must_use]
pub fn next_focus(&self) -> Option<ManagedWidget> {
self.widget
.next_focus
.get()
.next_focus()
.and_then(|next_focus| self.tree.widget(next_focus))
}
@ -449,6 +483,10 @@ impl ManagedWidget {
pub(crate) fn reset_child_layouts(&self) {
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 {
@ -592,3 +630,57 @@ impl WidgetRef {
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
}
}