mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
Use ElemId over raw pointers
This commit is contained in:
parent
665e71ba1f
commit
c8ad0abec4
4 changed files with 126 additions and 94 deletions
|
@ -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() {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue