mirror of
https://github.com/danbulant/cushy
synced 2026-07-04 02:30:42 +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::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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
35
src/tree.rs
35
src/tree.rs
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
114
src/widget.rs
114
src/widget.rs
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue