Use ElemId over raw pointers

This commit is contained in:
Richard Feldman 2022-03-05 17:08:26 -05:00
parent 665e71ba1f
commit c8ad0abec4
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
4 changed files with 126 additions and 94 deletions

View file

@ -1,79 +1,80 @@
use crate::roc::{RocElem, RocElemTag}; use crate::roc::{ElemId, RocElem, RocElemTag};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Focus { pub struct Focus {
focused: *const RocElem, focused: Option<ElemId>,
focused_ancestors: Vec<(*const RocElem, usize)>, focused_ancestors: Vec<(ElemId, usize)>,
} }
impl Default for Focus { impl Default for Focus {
fn default() -> Self { fn default() -> Self {
Self { Self {
focused: std::ptr::null(), focused: None,
focused_ancestors: Vec::new(), focused_ancestors: Vec::new(),
} }
} }
} }
impl Focus { impl Focus {
pub fn focused_elem(&self) -> *const RocElem { pub fn focused_elem(&self) -> Option<ElemId> {
self.focused self.focused
} }
/// e.g. the user pressed Tab. /// e.g. the user pressed Tab.
pub fn advance(&mut self, root: &RocElem) { ///
if self.focused.is_null() { /// This is in contrast to next_local, which advances within a button group.
// Nothing was focused in the first place, so try to focus the root. /// For example, if I have three radio buttons in a group, pressing the
if root.is_focusable() { /// arrow keys will cycle through them over and over without exiting the group -
self.focused = root as *const RocElem; /// whereas pressing Tab will cycle through them once and then exit the group.
self.focused_ancestors = Vec::new(); pub fn next_global(&mut self, root: &RocElem) {
} else if let Some((new_ptr, new_ancestors)) = match self.focused {
Self::next_focusable_sibling(root, None, None) Some(focused) => {
{ // while let Some((ancestor_id, index)) = self.focused_ancestors.pop() {
// If the root itself is not focusable, use its next focusable sibling. // let ancestor = ancestor_id.elem();
self.focused = new_ptr;
self.focused_ancestors = new_ancestors; // // TODO FIXME - right now this will re-traverse a lot of ground! To prevent this,
// // we should remember past indices searched, and tell the ancestors "hey stop searching when"
// // you reach these indices, because they were already covered previously.
// // One potentially easy way to do this: pass a min_index and max_index, and only look between those!
// //
// // Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing
// // `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and
// // can maybe even pass it in to make it clear what work has already been done!
// if let Some((new_id, new_ancestors)) =
// Self::next_focusable_sibling(focused, Some(ancestor), Some(index))
// {
// // We found the next element to focus, so record that.
// self.focused = Some(new_id);
// // We got a path to the new focusable's ancestor(s), so add them to the path.
// // (This may restore some of the ancestors we've been .pop()-ing as we iterated.)
// self.focused_ancestors.extend(new_ancestors);
// return;
// }
// // Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g.
// // what happens if it wraps around to a sibling? What happens if it wraps around to something
// // higher up the tree? Lower down the tree? What if nothing is focusable?
// // A separate question: what if we should have a separate text-to-speech concept separate from focus?
// }
} }
None => {
// Nothing was focused in the first place, so try to focus the root.
if root.is_focusable() {
self.focused = Some(root.id());
self.focused_ancestors = Vec::new();
} else if let Some((new_id, new_ancestors)) =
Self::next_focusable_sibling(root, None, None)
{
// If the root itself is not focusable, use its next focusable sibling.
self.focused = Some(new_id);
self.focused_ancestors = new_ancestors;
}
// Regardless of whether we found a focusable Elem, we're done. // Regardless of whether we found a focusable Elem, we're done.
return;
}
let focused = unsafe { &*self.focused };
while let Some((ancestor_ptr, index)) = self.focused_ancestors.pop() {
let ancestor = unsafe { &*ancestor_ptr };
// TODO FIXME - right now this will re-traverse a lot of ground! To prevent this,
// we should remember past indices searched, and tell the ancestors "hey stop searching when"
// you reach these indices, because they were already covered previously.
// One potentially easy way to do this: pass a min_index and max_index, and only look between those!
//
// Related idea: instead of doing .pop() here, iterate normally so we can `break;` after storing
// `new_ancestors = Some(next_ancestors);` - this way, we still have access to the full ancestry, and
// can maybe even pass it in to make it clear what work has already been done!
if let Some((new_ptr, new_ancestors)) =
Self::next_focusable_sibling(focused, Some(ancestor), Some(index))
{
debug_assert!(
!new_ptr.is_null(),
"next_focusable returned a null Elem pointer!"
);
// We found the next element to focus, so record that.
self.focused = new_ptr;
// We got a path to the new focusable's ancestor(s), so add them to the path.
// (This may restore some of the ancestors we've been .pop()-ing as we iterated.)
self.focused_ancestors.extend(new_ancestors);
return; return;
} }
// Need to write a bunch of tests for this, especially tests of focus wrapping around - e.g.
// what happens if it wraps around to a sibling? What happens if it wraps around to something
// higher up the tree? Lower down the tree? What if nothing is focusable?
// A separate question: what if we should have a separate text-to-speech concept separate from focus?
} }
} }
@ -84,7 +85,7 @@ impl Focus {
elem: &RocElem, elem: &RocElem,
ancestor: Option<&RocElem>, ancestor: Option<&RocElem>,
opt_index: Option<usize>, opt_index: Option<usize>,
) -> Option<(*const RocElem, Vec<(*const RocElem, usize)>)> { ) -> Option<(ElemId, Vec<(ElemId, usize)>)> {
use RocElemTag::*; use RocElemTag::*;
match elem.tag() { match elem.tag() {

View file

@ -1,5 +1,4 @@
use crate::{ use crate::{
focus::Focus,
graphics::{ graphics::{
colors::Rgba, colors::Rgba,
lowlevel::buffer::create_rect_buffers, lowlevel::buffer::create_rect_buffers,

View file

@ -6,7 +6,6 @@ mod roc;
use crate::roc::RocElem; use crate::roc::RocElem;
use core::alloc::Layout; use core::alloc::Layout;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use roc_std::RocStr;
extern "C" { extern "C" {
#[link_name = "roc__programForHost_1_exposed_generic"] #[link_name = "roc__programForHost_1_exposed_generic"]

View file

@ -48,6 +48,11 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
libc::memset(dst, c, n) libc::memset(dst, c, n)
} }
#[repr(transparent)]
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ElemId(*const RocElemEntry);
#[repr(transparent)] #[repr(transparent)]
#[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits #[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits
pub struct RocElem { pub struct RocElem {
@ -55,12 +60,18 @@ pub struct RocElem {
} }
impl RocElem { impl RocElem {
#[allow(unused)]
pub fn id(&self) -> ElemId {
ElemId(self.entry)
}
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
pub fn tag(&self) -> RocElemTag { pub fn tag(&self) -> RocElemTag {
// On a 64-bit system, the last 3 bits of the pointer store the tag // On a 64-bit system, the last 3 bits of the pointer store the tag
unsafe { mem::transmute::<u8, RocElemTag>((self.entry as u8) & 0b0000_0111) } unsafe { mem::transmute::<u8, RocElemTag>((self.entry as u8) & 0b0000_0111) }
} }
#[allow(unused)]
pub fn entry(&self) -> &RocElemEntry { pub fn entry(&self) -> &RocElemEntry {
unsafe { &*self.entry_ptr() } unsafe { &*self.entry_ptr() }
} }
@ -72,53 +83,54 @@ impl RocElem {
cleared as *const RocElemEntry cleared as *const RocElemEntry
} }
fn diff(self, other: RocElem, patches: &mut Vec<(usize, Patch)>, index: usize) { // fn diff(self, other: RocElem, patches: &mut Vec<(usize, Patch)>, index: usize) {
use RocElemTag::*; // use RocElemTag::*;
let tag = self.tag(); // let tag = self.tag();
if tag != other.tag() { // if tag != other.tag() {
// They were totally different elem types! // // They were totally different elem types!
// TODO should we handle Row -> Col or Col -> Row differently? // // TODO should we handle Row -> Col or Col -> Row differently?
// Elm doesn't: https://github.com/elm/virtual-dom/blob/5a5bcf48720bc7d53461b3cd42a9f19f119c5503/src/Elm/Kernel/VirtualDom.js#L714 // // Elm doesn't: https://github.com/elm/virtual-dom/blob/5a5bcf48720bc7d53461b3cd42a9f19f119c5503/src/Elm/Kernel/VirtualDom.js#L714
return; // return;
} // }
match tag { // match tag {
Button => unsafe { // Button => unsafe {
let button_self = &*self.entry().button; // let button_self = &*self.entry().button;
let button_other = &*other.entry().button; // let button_other = &*other.entry().button;
// TODO compute a diff and patch for the button // // TODO compute a diff and patch for the button
}, // },
Text => unsafe { // Text => unsafe {
let str_self = &*self.entry().text; // let str_self = &*self.entry().text;
let str_other = &*other.entry().text; // let str_other = &*other.entry().text;
if str_self != str_other { // if str_self != str_other {
todo!("fix this"); // todo!("fix this");
// let roc_str = other.entry().text; // // let roc_str = other.entry().text;
// let patch = Patch::Text(ManuallyDrop::into_inner(roc_str)); // // let patch = Patch::Text(ManuallyDrop::into_inner(roc_str));
// patches.push((index, patch)); // // patches.push((index, patch));
} // }
}, // },
Row => unsafe { // Row => unsafe {
let children_self = &self.entry().row_or_col.children; // let children_self = &self.entry().row_or_col.children;
let children_other = &other.entry().row_or_col.children; // let children_other = &other.entry().row_or_col.children;
// TODO diff children // // TODO diff children
}, // },
Col => unsafe { // Col => unsafe {
let children_self = &self.entry().row_or_col.children; // let children_self = &self.entry().row_or_col.children;
let children_other = &other.entry().row_or_col.children; // let children_other = &other.entry().row_or_col.children;
// TODO diff children // // TODO diff children
}, // },
} // }
} // }
#[allow(unused)]
pub fn is_focusable(&self) -> bool { pub fn is_focusable(&self) -> bool {
use RocElemTag::*; use RocElemTag::*;
@ -128,6 +140,26 @@ impl RocElem {
} }
} }
#[allow(unused)]
pub fn row<T: Into<RocList<RocElem>>>(children: T) -> RocElem {
Self::elem_from_tag(Self::row_or_col(children), RocElemTag::Row)
}
#[allow(unused)]
pub fn col<T: Into<RocList<RocElem>>>(children: T) -> RocElem {
Self::elem_from_tag(Self::row_or_col(children), RocElemTag::Col)
}
fn row_or_col<T: Into<RocList<RocElem>>>(children: T) -> RocElemEntry {
let row_or_col = RocRowOrCol {
children: children.into(),
};
RocElemEntry {
row_or_col: ManuallyDrop::new(row_or_col),
}
}
#[allow(unused)]
pub fn button(styles: ButtonStyles, child: RocElem) -> RocElem { pub fn button(styles: ButtonStyles, child: RocElem) -> RocElem {
let button = RocButton { let button = RocButton {
child: ManuallyDrop::new(child), child: ManuallyDrop::new(child),
@ -140,6 +172,7 @@ impl RocElem {
Self::elem_from_tag(entry, RocElemTag::Button) Self::elem_from_tag(entry, RocElemTag::Button)
} }
#[allow(unused)]
pub fn text<T: Into<RocStr>>(into_roc_str: T) -> RocElem { pub fn text<T: Into<RocStr>>(into_roc_str: T) -> RocElem {
let entry = RocElemEntry { let entry = RocElemEntry {
text: ManuallyDrop::new(into_roc_str.into()), text: ManuallyDrop::new(into_roc_str.into()),