Skip to content

Commit

Permalink
Next-focus progress
Browse files Browse the repository at this point in the history
gameui no longer needs next_focus for the input to be focused
automatically
  • Loading branch information
ecton committed Nov 7, 2023
1 parent e7b4fe0 commit 8a2dae3
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 47 deletions.
37 changes: 15 additions & 22 deletions examples/gameui.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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| {
Expand All @@ -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()
}
78 changes: 75 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()) {
Expand All @@ -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> {
Expand Down
35 changes: 22 additions & 13 deletions src/tree.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -220,6 +234,7 @@ struct TreeData {
focus: Option<WidgetId>,
hover: Option<WidgetId>,
render_order: Vec<WidgetId>,
previous_focuses: HashMap<WidgetId, WidgetId>,
}

impl TreeData {
Expand All @@ -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);
Expand Down Expand Up @@ -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))
}
}
Loading

0 comments on commit 8a2dae3

Please sign in to comment.