mirror of
https://github.com/danbulant/cushy
synced 2026-06-18 05:51:20 +00:00
Next-focus progress
gameui no longer needs next_focus for the input to be focused automatically
This commit is contained in:
parent
e7b4fe00b6
commit
8a2dae3f76
4 changed files with 215 additions and 49 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
35
src/tree.rs
35
src/tree.rs
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
114
src/widget.rs
114
src/widget.rs
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue