diff --git a/examples/gui/platform/src/focus.rs b/examples/gui/platform/src/focus.rs index 4d844fdd6a..1cd72d5fd9 100644 --- a/examples/gui/platform/src/focus.rs +++ b/examples/gui/platform/src/focus.rs @@ -1,79 +1,80 @@ -use crate::roc::{RocElem, RocElemTag}; +use crate::roc::{ElemId, RocElem, RocElemTag}; #[derive(Debug, PartialEq, Eq)] pub struct Focus { - focused: *const RocElem, - focused_ancestors: Vec<(*const RocElem, usize)>, + focused: Option, + focused_ancestors: Vec<(ElemId, usize)>, } impl Default for Focus { fn default() -> Self { Self { - focused: std::ptr::null(), + focused: None, focused_ancestors: Vec::new(), } } } impl Focus { - pub fn focused_elem(&self) -> *const RocElem { + pub fn focused_elem(&self) -> Option { self.focused } /// e.g. the user pressed Tab. - pub fn advance(&mut self, root: &RocElem) { - if self.focused.is_null() { - // Nothing was focused in the first place, so try to focus the root. - if root.is_focusable() { - self.focused = root as *const RocElem; - self.focused_ancestors = Vec::new(); - } else if let Some((new_ptr, new_ancestors)) = - Self::next_focusable_sibling(root, None, None) - { - // If the root itself is not focusable, use its next focusable sibling. - self.focused = new_ptr; - self.focused_ancestors = new_ancestors; + /// + /// This is in contrast to next_local, which advances within a button group. + /// For example, if I have three radio buttons in a group, pressing the + /// arrow keys will cycle through them over and over without exiting the group - + /// whereas pressing Tab will cycle through them once and then exit the group. + pub fn next_global(&mut self, root: &RocElem) { + match self.focused { + Some(focused) => { + // while let Some((ancestor_id, index)) = self.focused_ancestors.pop() { + // let ancestor = ancestor_id.elem(); + + // // 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. - 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); - + // Regardless of whether we found a focusable Elem, we're done. 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, ancestor: Option<&RocElem>, opt_index: Option, - ) -> Option<(*const RocElem, Vec<(*const RocElem, usize)>)> { + ) -> Option<(ElemId, Vec<(ElemId, usize)>)> { use RocElemTag::*; match elem.tag() { diff --git a/examples/gui/platform/src/gui.rs b/examples/gui/platform/src/gui.rs index d811eeb552..66c55e0746 100644 --- a/examples/gui/platform/src/gui.rs +++ b/examples/gui/platform/src/gui.rs @@ -1,5 +1,4 @@ use crate::{ - focus::Focus, graphics::{ colors::Rgba, lowlevel::buffer::create_rect_buffers, diff --git a/examples/gui/platform/src/lib.rs b/examples/gui/platform/src/lib.rs index f609cd518a..7e1634b34f 100644 --- a/examples/gui/platform/src/lib.rs +++ b/examples/gui/platform/src/lib.rs @@ -6,7 +6,6 @@ mod roc; use crate::roc::RocElem; use core::alloc::Layout; use core::mem::MaybeUninit; -use roc_std::RocStr; extern "C" { #[link_name = "roc__programForHost_1_exposed_generic"] diff --git a/examples/gui/platform/src/roc.rs b/examples/gui/platform/src/roc.rs index 005ca4e6ba..d4f2d5e3d9 100644 --- a/examples/gui/platform/src/roc.rs +++ b/examples/gui/platform/src/roc.rs @@ -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) } +#[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)] #[cfg(target_pointer_width = "64")] // on a 64-bit system, the tag fits in this pointer's spare 3 bits pub struct RocElem { @@ -55,12 +60,18 @@ pub struct RocElem { } impl RocElem { + #[allow(unused)] + pub fn id(&self) -> ElemId { + ElemId(self.entry) + } + #[cfg(target_pointer_width = "64")] pub fn tag(&self) -> RocElemTag { // On a 64-bit system, the last 3 bits of the pointer store the tag unsafe { mem::transmute::((self.entry as u8) & 0b0000_0111) } } + #[allow(unused)] pub fn entry(&self) -> &RocElemEntry { unsafe { &*self.entry_ptr() } } @@ -72,53 +83,54 @@ impl RocElem { cleared as *const RocElemEntry } - fn diff(self, other: RocElem, patches: &mut Vec<(usize, Patch)>, index: usize) { - use RocElemTag::*; + // fn diff(self, other: RocElem, patches: &mut Vec<(usize, Patch)>, index: usize) { + // use RocElemTag::*; - let tag = self.tag(); + // let tag = self.tag(); - if tag != other.tag() { - // They were totally different elem types! + // if tag != other.tag() { + // // They were totally different elem types! - // 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 - return; - } + // // 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 + // return; + // } - match tag { - Button => unsafe { - let button_self = &*self.entry().button; - let button_other = &*other.entry().button; + // match tag { + // Button => unsafe { + // let button_self = &*self.entry().button; + // let button_other = &*other.entry().button; - // TODO compute a diff and patch for the button - }, - Text => unsafe { - let str_self = &*self.entry().text; - let str_other = &*other.entry().text; + // // TODO compute a diff and patch for the button + // }, + // Text => unsafe { + // let str_self = &*self.entry().text; + // let str_other = &*other.entry().text; - if str_self != str_other { - todo!("fix this"); - // let roc_str = other.entry().text; - // let patch = Patch::Text(ManuallyDrop::into_inner(roc_str)); + // if str_self != str_other { + // todo!("fix this"); + // // let roc_str = other.entry().text; + // // let patch = Patch::Text(ManuallyDrop::into_inner(roc_str)); - // patches.push((index, patch)); - } - }, - Row => unsafe { - let children_self = &self.entry().row_or_col.children; - let children_other = &other.entry().row_or_col.children; + // // patches.push((index, patch)); + // } + // }, + // Row => unsafe { + // let children_self = &self.entry().row_or_col.children; + // let children_other = &other.entry().row_or_col.children; - // TODO diff children - }, - Col => unsafe { - let children_self = &self.entry().row_or_col.children; - let children_other = &other.entry().row_or_col.children; + // // TODO diff children + // }, + // Col => unsafe { + // let children_self = &self.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 { use RocElemTag::*; @@ -128,6 +140,26 @@ impl RocElem { } } + #[allow(unused)] + pub fn row>>(children: T) -> RocElem { + Self::elem_from_tag(Self::row_or_col(children), RocElemTag::Row) + } + + #[allow(unused)] + pub fn col>>(children: T) -> RocElem { + Self::elem_from_tag(Self::row_or_col(children), RocElemTag::Col) + } + + fn row_or_col>>(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 { let button = RocButton { child: ManuallyDrop::new(child), @@ -140,6 +172,7 @@ impl RocElem { Self::elem_from_tag(entry, RocElemTag::Button) } + #[allow(unused)] pub fn text>(into_roc_str: T) -> RocElem { let entry = RocElemEntry { text: ManuallyDrop::new(into_roc_str.into()),