mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Breakout: Button → Rect
This commit is contained in:
parent
a111f510a4
commit
fee7fd7956
6 changed files with 8 additions and 335 deletions
|
@ -15,16 +15,4 @@ render = \state ->
|
||||||
height = if state.height == 1000 then "correct!" else if state.height == 0 then "zero" else "incorrect"
|
height = if state.height == 1000 then "correct!" else if state.height == 0 then "zero" else "incorrect"
|
||||||
width = if state.width == 1900 then "Correct!" else if state.width == 0 then "zero" else "Incorrect"
|
width = if state.width == 1900 then "Correct!" else if state.width == 0 then "zero" else "Incorrect"
|
||||||
|
|
||||||
Col
|
Rect (Text "Success! ") styles
|
||||||
[
|
|
||||||
Row
|
|
||||||
[
|
|
||||||
Button (Text "Corner ") styles,
|
|
||||||
Button (Text "Top Mid ") { styles & bgColor: rgba 100 100 50 1 },
|
|
||||||
Button (Text "Top Right ") { styles & bgColor: rgba 50 50 150 1 },
|
|
||||||
],
|
|
||||||
Button (Text "Mid Left ") { styles & bgColor: rgba 150 100 100 1 },
|
|
||||||
Button (Text "Bottom Left") { styles & bgColor: rgba 150 50 50 1 },
|
|
||||||
Button (Text "height: \(height)") { styles & bgColor: rgba 50 150 50 1 },
|
|
||||||
Button (Text "width: \(width)") { styles & bgColor: rgba 50 100 50 1 },
|
|
||||||
]
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ Rgba : { r : F32, g : F32, b : F32, a : F32 }
|
||||||
|
|
||||||
ButtonStyles : { bgColor : Rgba, borderColor : Rgba, borderWidth : F32, textColor : Rgba }
|
ButtonStyles : { bgColor : Rgba, borderColor : Rgba, borderWidth : F32, textColor : Rgba }
|
||||||
|
|
||||||
Elem : [ Button Elem ButtonStyles, Col (List Elem), Row (List Elem), Text Str ]
|
Elem : [ Rect Elem ButtonStyles, Text Str ]
|
||||||
|
|
||||||
State : { width : F32, height : F32 }
|
State : { width : F32, height : F32 }
|
||||||
|
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
use crate::roc::{ElemId, RocElem, RocElemTag};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct Focus {
|
|
||||||
focused: Option<ElemId>,
|
|
||||||
focused_ancestors: Vec<(ElemId, usize)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Focus {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
focused: None,
|
|
||||||
focused_ancestors: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Focus {
|
|
||||||
pub fn focused_elem(&self) -> Option<ElemId> {
|
|
||||||
self.focused
|
|
||||||
}
|
|
||||||
|
|
||||||
/// e.g. the user pressed Tab.
|
|
||||||
///
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the next focusable sibling element after this one.
|
|
||||||
/// If this element has no siblings, or no *next* sibling after the given index
|
|
||||||
/// (e.g. the given index refers to the last element in a Row element), return None.
|
|
||||||
fn next_focusable_sibling(
|
|
||||||
elem: &RocElem,
|
|
||||||
ancestor: Option<&RocElem>,
|
|
||||||
opt_index: Option<usize>,
|
|
||||||
) -> Option<(ElemId, Vec<(ElemId, usize)>)> {
|
|
||||||
use RocElemTag::*;
|
|
||||||
|
|
||||||
match elem.tag() {
|
|
||||||
Button | Text => None,
|
|
||||||
Row | Col => {
|
|
||||||
let children = unsafe { &elem.entry().row_or_col.children.as_slice() };
|
|
||||||
let iter = match opt_index {
|
|
||||||
Some(focus_index) => children[0..focus_index].iter(),
|
|
||||||
None => children.iter(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for child in iter {
|
|
||||||
if let Some(focused) = Self::next_focusable_sibling(child, ancestor, None) {
|
|
||||||
return Some(focused);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_global_button_root() {
|
|
||||||
use crate::roc::{ButtonStyles, RocElem};
|
|
||||||
|
|
||||||
let child = RocElem::text("");
|
|
||||||
let root = RocElem::button(ButtonStyles::default(), child);
|
|
||||||
let mut focus = Focus::default();
|
|
||||||
|
|
||||||
// At first, nothing should be focused.
|
|
||||||
assert_eq!(focus.focused_elem(), None);
|
|
||||||
|
|
||||||
focus.next_global(&root);
|
|
||||||
|
|
||||||
// Buttons should be focusable, so advancing focus should give the button focus.
|
|
||||||
assert_eq!(focus.focused_elem(), Some(root.id()));
|
|
||||||
|
|
||||||
// Since the button is at the root, advancing again should maintain focus on it.
|
|
||||||
focus.next_global(&root);
|
|
||||||
assert_eq!(focus.focused_elem(), Some(root.id()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_global_text_root() {
|
|
||||||
let root = RocElem::text("");
|
|
||||||
let mut focus = Focus::default();
|
|
||||||
|
|
||||||
// At first, nothing should be focused.
|
|
||||||
assert_eq!(focus.focused_elem(), None);
|
|
||||||
|
|
||||||
focus.next_global(&root);
|
|
||||||
|
|
||||||
// Text should not be focusable, so advancing focus should have no effect here.
|
|
||||||
assert_eq!(focus.focused_elem(), None);
|
|
||||||
|
|
||||||
// Just to double-check, advancing a second time should not change this.
|
|
||||||
focus.next_global(&root);
|
|
||||||
assert_eq!(focus.focused_elem(), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn next_global_row() {
|
|
||||||
use crate::roc::{ButtonStyles, RocElem};
|
|
||||||
|
|
||||||
let child = RocElem::text("");
|
|
||||||
let button = RocElem::button(ButtonStyles::default(), child);
|
|
||||||
let button_id = button.id();
|
|
||||||
let root = RocElem::row(&[button] as &[_]);
|
|
||||||
let mut focus = Focus::default();
|
|
||||||
|
|
||||||
// At first, nothing should be focused.
|
|
||||||
assert_eq!(focus.focused_elem(), None);
|
|
||||||
|
|
||||||
focus.next_global(&root);
|
|
||||||
|
|
||||||
// Buttons should be focusable, so advancing focus should give the first button in the row focus.
|
|
||||||
assert_eq!(focus.focused_elem(), Some(button_id));
|
|
||||||
|
|
||||||
// Since the button is the only element in the row, advancing again should maintain focus on it.
|
|
||||||
focus.next_global(&root);
|
|
||||||
assert_eq!(focus.focused_elem(), Some(button_id));
|
|
||||||
}
|
|
|
@ -482,7 +482,7 @@ fn to_drawable(
|
||||||
let is_focused = focused_elem == elem as *const RocElem;
|
let is_focused = focused_elem == elem as *const RocElem;
|
||||||
|
|
||||||
match elem.tag() {
|
match elem.tag() {
|
||||||
Button => {
|
Rect => {
|
||||||
let button = unsafe { &elem.entry().button };
|
let button = unsafe { &elem.entry().button };
|
||||||
let styles = button.styles;
|
let styles = button.styles;
|
||||||
let (child_bounds, child_drawable) =
|
let (child_bounds, child_drawable) =
|
||||||
|
@ -546,66 +546,6 @@ fn to_drawable(
|
||||||
|
|
||||||
(text_bounds, drawable)
|
(text_bounds, drawable)
|
||||||
}
|
}
|
||||||
Row => {
|
|
||||||
let row = unsafe { &elem.entry().row_or_col };
|
|
||||||
let mut final_bounds = Bounds::default();
|
|
||||||
let mut offset: Vector2<f32> = (0.0, 0.0).into();
|
|
||||||
let mut offset_entries = Vec::with_capacity(row.children.len());
|
|
||||||
|
|
||||||
for child in row.children.as_slice().iter() {
|
|
||||||
let (child_bounds, child_drawable) =
|
|
||||||
to_drawable(&child, focused_elem, bounds, glyph_brush);
|
|
||||||
|
|
||||||
offset_entries.push((offset, child_drawable));
|
|
||||||
|
|
||||||
// Make sure the final height is enough to fit this child
|
|
||||||
final_bounds.height = final_bounds.height.max(child_bounds.height);
|
|
||||||
|
|
||||||
// Add the child's width to the final width
|
|
||||||
final_bounds.width = final_bounds.width + child_bounds.width;
|
|
||||||
|
|
||||||
// Offset the next child to make sure it appears after this one.
|
|
||||||
offset.x += child_bounds.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
final_bounds,
|
|
||||||
Drawable {
|
|
||||||
bounds: final_bounds,
|
|
||||||
content: DrawableContent::Offset(offset_entries),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Col => {
|
|
||||||
let col = unsafe { &elem.entry().row_or_col };
|
|
||||||
let mut final_bounds = Bounds::default();
|
|
||||||
let mut offset: Vector2<f32> = (0.0, 0.0).into();
|
|
||||||
let mut offset_entries = Vec::with_capacity(col.children.len());
|
|
||||||
|
|
||||||
for child in col.children.as_slice().iter() {
|
|
||||||
let (child_bounds, child_drawable) =
|
|
||||||
to_drawable(&child, focused_elem, bounds, glyph_brush);
|
|
||||||
|
|
||||||
offset_entries.push((offset, child_drawable));
|
|
||||||
|
|
||||||
// Make sure the final width is enough to fit this child
|
|
||||||
final_bounds.width = final_bounds.width.max(child_bounds.width);
|
|
||||||
|
|
||||||
// Add the child's height to the final height
|
|
||||||
final_bounds.height = final_bounds.height + child_bounds.height;
|
|
||||||
|
|
||||||
// Offset the next child to make sure it appears after this one.
|
|
||||||
offset.y += child_bounds.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
final_bounds,
|
|
||||||
Drawable {
|
|
||||||
bounds: final_bounds,
|
|
||||||
content: DrawableContent::Offset(offset_entries),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
mod focus;
|
|
||||||
mod graphics;
|
mod graphics;
|
||||||
mod gui;
|
mod gui;
|
||||||
mod roc;
|
mod roc;
|
||||||
|
|
|
@ -93,20 +93,6 @@ impl Debug for RocElem {
|
||||||
match self.tag() {
|
match self.tag() {
|
||||||
Button => unsafe { &*self.entry().button }.fmt(f),
|
Button => unsafe { &*self.entry().button }.fmt(f),
|
||||||
Text => unsafe { &*self.entry().text }.fmt(f),
|
Text => unsafe { &*self.entry().text }.fmt(f),
|
||||||
Row => {
|
|
||||||
let row_or_col = unsafe { &*self.entry().row_or_col };
|
|
||||||
|
|
||||||
f.debug_struct("RocRow")
|
|
||||||
.field("children", &row_or_col.children)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
Col => {
|
|
||||||
let row_or_col = unsafe { &*self.entry().row_or_col };
|
|
||||||
|
|
||||||
f.debug_struct("RocCol")
|
|
||||||
.field("children", &row_or_col.children)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -182,35 +168,6 @@ impl RocElem {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn is_focusable(&self) -> bool {
|
|
||||||
use RocElemTag::*;
|
|
||||||
|
|
||||||
match self.tag() {
|
|
||||||
Button => true,
|
|
||||||
Text | Row | Col => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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)]
|
#[allow(unused)]
|
||||||
pub fn button(styles: ButtonStyles, child: RocElem) -> RocElem {
|
pub fn button(styles: ButtonStyles, child: RocElem) -> RocElem {
|
||||||
let button = RocButton {
|
let button = RocButton {
|
||||||
|
@ -221,7 +178,7 @@ impl RocElem {
|
||||||
button: ManuallyDrop::new(button),
|
button: ManuallyDrop::new(button),
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::elem_from_tag(entry, RocElemTag::Button)
|
Self::elem_from_tag(entry, RocElemTag::Rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
@ -255,9 +212,7 @@ impl RocElem {
|
||||||
#[allow(unused)] // This is actually used, just via a mem::transmute from u8
|
#[allow(unused)] // This is actually used, just via a mem::transmute from u8
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum RocElemTag {
|
pub enum RocElemTag {
|
||||||
Button = 0,
|
Rect = 0,
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
Text,
|
Text,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,26 +223,14 @@ pub struct RocButton {
|
||||||
pub styles: ButtonStyles,
|
pub styles: ButtonStyles,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct RocRowOrCol {
|
|
||||||
pub children: RocList<RocElem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl ReferenceCount for RocElem {
|
unsafe impl ReferenceCount for RocElem {
|
||||||
/// Increment the reference count.
|
/// Increment the reference count.
|
||||||
fn increment(&self) {
|
fn increment(&self) {
|
||||||
use RocElemTag::*;
|
use RocElemTag::*;
|
||||||
|
|
||||||
match self.tag() {
|
match self.tag() {
|
||||||
Button => unsafe { &*self.entry().button.child }.increment(),
|
Rect => unsafe { &*self.entry().button.child }.increment(),
|
||||||
Text => unsafe { &*self.entry().text }.increment(),
|
Text => unsafe { &*self.entry().text }.increment(),
|
||||||
Row | Col => {
|
|
||||||
let children = unsafe { &self.entry().row_or_col.children };
|
|
||||||
|
|
||||||
for child in children.as_slice().iter() {
|
|
||||||
child.increment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,15 +246,8 @@ unsafe impl ReferenceCount for RocElem {
|
||||||
let elem = &*ptr;
|
let elem = &*ptr;
|
||||||
|
|
||||||
match elem.tag() {
|
match elem.tag() {
|
||||||
Button => ReferenceCount::decrement(&*elem.entry().button.child),
|
Rect => ReferenceCount::decrement(&*elem.entry().button.child),
|
||||||
Text => ReferenceCount::decrement(&*elem.entry().text),
|
Text => ReferenceCount::decrement(&*elem.entry().text),
|
||||||
Row | Col => {
|
|
||||||
let children = &elem.entry().row_or_col.children;
|
|
||||||
|
|
||||||
for child in children.as_slice().iter() {
|
|
||||||
ReferenceCount::decrement(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,7 +265,6 @@ pub struct ButtonStyles {
|
||||||
pub union RocElemEntry {
|
pub union RocElemEntry {
|
||||||
pub button: ManuallyDrop<RocButton>,
|
pub button: ManuallyDrop<RocButton>,
|
||||||
pub text: ManuallyDrop<RocStr>,
|
pub text: ManuallyDrop<RocStr>,
|
||||||
pub row_or_col: ManuallyDrop<RocRowOrCol>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// enum Patch {
|
// enum Patch {
|
||||||
|
@ -348,24 +283,7 @@ fn make_button() {
|
||||||
let text = RocElem::text("blah");
|
let text = RocElem::text("blah");
|
||||||
let button = RocElem::button(ButtonStyles::default(), text);
|
let button = RocElem::button(ButtonStyles::default(), text);
|
||||||
|
|
||||||
assert_eq!(button.tag(), RocElemTag::Button);
|
assert_eq!(button.tag(), RocElemTag::Rect);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn make_row_with_text() {
|
|
||||||
let text = RocElem::text("");
|
|
||||||
let row = RocElem::row(&[text] as &[_]);
|
|
||||||
|
|
||||||
assert_eq!(row.tag(), RocElemTag::Row);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn make_row_with_button() {
|
|
||||||
let text = RocElem::text("");
|
|
||||||
let button = RocElem::button(ButtonStyles::default(), text);
|
|
||||||
let row = RocElem::row(&[button] as &[_]);
|
|
||||||
|
|
||||||
assert_eq!(row.tag(), RocElemTag::Row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_render(state: State) -> RocElem {
|
pub fn app_render(state: State) -> RocElem {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue