ui layouting in rust

rust-rewrite
Patrick Cleavelin 2024-02-26 21:27:15 -06:00
parent 4beed39921
commit 8bb3cc6358
2 changed files with 365 additions and 40 deletions

View File

@ -3,6 +3,8 @@
use std::collections::HashMap; use std::collections::HashMap;
const ROOT_NODE: &str = "root"; const ROOT_NODE: &str = "root";
const FONT_WIDTH: usize = 8;
const FONT_HEIGHT: usize = 16;
type NodeIndex = usize; type NodeIndex = usize;
@ -26,12 +28,12 @@ impl NodeKey {
} }
pub fn new(cx: &Context, label: &str) -> Self { pub fn new(cx: &Context, label: &str) -> Self {
NodeKey(format!("{}:{label}", cx.node_ref(cx.current_parent).label)) NodeKey(format!("{}:{label}", cx.node_ref(cx.current_parent).key))
} }
} }
#[derive(Debug, Default, Clone, Copy)] #[derive(Debug, Default, Clone, Copy)]
enum SemanticSize { pub enum SemanticSize {
#[default] #[default]
FitText, FitText,
ChildrenSum, ChildrenSum,
@ -40,26 +42,36 @@ enum SemanticSize {
PercentOfParent(i32), PercentOfParent(i32),
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone, Copy)]
enum Axis { #[repr(usize)]
pub enum Axis {
#[default] #[default]
Horizontal, Horizontal,
Vertical, Vertical,
} }
#[derive(Debug, Default, Clone, Copy)]
pub struct Size {
pub axis: Axis,
pub semantic_size: [SemanticSize; 2],
pub computed_size: [i32; 2],
pub computed_pos: [i32; 2],
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct PersistentNodeData { pub struct PersistentNodeData {
axis: Axis, pub label: String,
semantic_size: [SemanticSize; 2], pub size: Size,
computed_size: [i32; 2],
computed_pos: [i32; 2],
} }
#[derive(Debug)] #[derive(Debug)]
struct FrameNode { struct FrameNode {
index: NodeIndex,
key: NodeKey, key: NodeKey,
label: String, label: String,
size: Size,
first: Option<NodeIndex>, first: Option<NodeIndex>,
last: Option<NodeIndex>, last: Option<NodeIndex>,
next: Option<NodeIndex>, next: Option<NodeIndex>,
@ -70,8 +82,10 @@ struct FrameNode {
impl FrameNode { impl FrameNode {
fn root() -> Self { fn root() -> Self {
Self { Self {
index: 0,
key: NodeKey::root(), key: NodeKey::root(),
label: "root".to_string(), label: "root".to_string(),
size: Size::default(),
first: None, first: None,
last: None, last: None,
next: None, next: None,
@ -102,6 +116,10 @@ impl Context {
} }
} }
pub fn node_iter(&self) -> impl Iterator<Item = &'_ PersistentNodeData> {
PersistentIter::from_context(self)
}
// TODO: refactor to not panic, return option // TODO: refactor to not panic, return option
/// Panics on out-of-bounds index /// Panics on out-of-bounds index
fn node_ref(&self, index: NodeIndex) -> &FrameNode { fn node_ref(&self, index: NodeIndex) -> &FrameNode {
@ -148,12 +166,22 @@ impl Context {
if let Some(_node) = self.persistent.get(&key) { if let Some(_node) = self.persistent.get(&key) {
// TODO: check for last_interacted_index and invalidate persistent data // TODO: check for last_interacted_index and invalidate persistent data
unimplemented!("no persistent nodes for you");
} else { } else {
self.persistent self.persistent.insert(
.insert(key.clone(), PersistentNodeData::default()); key.clone(),
PersistentNodeData {
label: label.clone(),
..Default::default()
},
);
} }
let this_index = self.frame_nodes.len();
let frame_node = FrameNode { let frame_node = FrameNode {
size: self.persistent.get(&key).expect("guaranteed to exist").size,
index: this_index,
key, key,
label, label,
first: None, first: None,
@ -178,32 +206,284 @@ impl Context {
this_index this_index
} }
pub fn _make_node_with_semantic_size(
&mut self,
label: impl ToString,
semantic_size: [SemanticSize; 2],
) -> NodeIndex {
let index = self.make_node(label);
let node = self.node_ref_mut(index);
node.size.semantic_size = semantic_size;
index
}
pub fn push_parent(&mut self, key: NodeIndex) { pub fn push_parent(&mut self, key: NodeIndex) {
self.current_parent = key; self.current_parent = key;
} }
pub fn pop_parent(&mut self) { pub fn pop_parent(&mut self) {
self.current_parent = self.node_ref(self.current_parent).parent.unwrap_or(0); let Some(parent) = self.frame_nodes.last().and_then(|node| node.parent) else {
return;
};
self.current_parent = self.node_ref(parent).parent.unwrap_or(0);
} }
pub fn debug_print(&self) { pub fn debug_print(&self) {
let iter = NodeIter::from_index(&self.frame_nodes, 0); let iter = NodeIter::from_index(&self.frame_nodes, 0, true);
for node in iter { for node in iter {
eprintln!("{node:?}"); let Some(_persistent) = self.persistent.get(&node.key) else {
continue;
};
eprintln!("{node:#?}");
} }
} }
pub fn update_layout(&mut self) { pub fn prune(&mut self) {
let iter = NodeIter::from_index(&self.frame_nodes, 0); self.frame_nodes.clear();
}
fn ancestor_size(&self, index: NodeIndex, axis: Axis) -> i32 {
if let Some(parent) = self.node_ref(index).parent {
let parent_node = self.node_ref(parent);
match parent_node.size.semantic_size[axis as usize] {
SemanticSize::FitText
| SemanticSize::Fill
| SemanticSize::Exact(_)
| SemanticSize::PercentOfParent(_) => {
return parent_node.size.computed_size[axis as usize];
}
SemanticSize::ChildrenSum => return self.ancestor_size(parent, axis),
}
}
// TODO: change this panic to something else less catastrophic
// should never get here if everything else is working properly
panic!("no ancestor size");
0
}
pub fn update_layout(&mut self, canvas_size: [i32; 2], index: NodeIndex) {
let mut post_compute_horizontal = false;
let mut post_compute_vertical = false;
{
let mut parent_axis = Axis::default();
if let Some(parent_index) = self.frame_nodes[index].parent {
let parent_node = self.node_ref(parent_index);
parent_axis = parent_node.size.axis;
self.frame_nodes[index].size.computed_pos = parent_node.size.computed_pos;
}
if let Some(prev_node) = self.frame_nodes[index]
.prev
.map(|index| self.node_ref(index))
{
let prev_pos = prev_node.size.computed_pos;
let prev_size = prev_node.size.computed_size;
self.frame_nodes[index].size.computed_pos[parent_axis as usize] =
prev_pos[parent_axis as usize] + prev_size[parent_axis as usize];
}
if self.frame_nodes[index].key.0.as_str() == "root" {
self.frame_nodes[index].size.computed_size = canvas_size;
} else {
match self.frame_nodes[index].size.semantic_size[0] {
SemanticSize::FitText => {
self.frame_nodes[index].size.computed_size[0] =
(self.frame_nodes[index].label.len() * FONT_WIDTH) as i32;
}
SemanticSize::ChildrenSum => {
post_compute_horizontal = true;
}
SemanticSize::Fill => (),
SemanticSize::Exact(size) => {
self.frame_nodes[index].size.computed_size[0] = size
}
SemanticSize::PercentOfParent(percent) => {
let size = ((self
.ancestor_size(self.frame_nodes[index].index, Axis::Horizontal)
as f32)
* (percent as f32)
/ 100.0) as i32;
self.frame_nodes[index].size.computed_size[0] = size;
}
}
match self.frame_nodes[index].size.semantic_size[1] {
SemanticSize::FitText => {
self.frame_nodes[index].size.computed_size[1] = FONT_HEIGHT as i32;
}
SemanticSize::ChildrenSum => {
post_compute_vertical = true;
}
SemanticSize::Fill => (),
SemanticSize::Exact(size) => {
self.frame_nodes[index].size.computed_size[1] = size
}
SemanticSize::PercentOfParent(percent) => {
let size = ((self
.ancestor_size(self.frame_nodes[index].index, Axis::Vertical)
as f32)
* (percent as f32)
/ 100.0) as i32;
self.frame_nodes[index].size.computed_size[Axis::Vertical as usize] = size;
}
}
}
}
// let there be the braces of lifetimes
{
if let Some(first_child_index) = self.frame_nodes.get(index).and_then(|node| node.first)
{
let mut child_size: [i32; 2] = [0; 2];
let mut number_of_fills = [1; 2];
number_of_fills[self.frame_nodes[index].size.axis as usize] = 0;
let mut i = first_child_index;
loop {
self.update_layout(canvas_size, i);
let child_node = self.node_ref(i);
if matches!(
child_node.size.semantic_size[self.frame_nodes[index].size.axis as usize],
SemanticSize::Fill
) {
number_of_fills[self.frame_nodes[index].size.axis as usize] += 1;
} else {
child_size[self.frame_nodes[index].size.axis as usize] += child_node
.size
.computed_size[self.frame_nodes[index].size.axis as usize];
}
let Some(next) = self.node_ref(i).next else {
break;
};
i = next;
}
// update nodes with `Fill` with their new computed size
let mut i = first_child_index;
loop {
let node_size = self.frame_nodes[index].size.computed_size;
let child_node = self.node_ref_mut(i);
for axis in 0..2 {
if matches!(child_node.size.semantic_size[axis], SemanticSize::Fill) {
child_node.size.computed_size[axis] =
(node_size[axis] - child_size[axis]) / number_of_fills[axis];
}
}
self.update_layout(canvas_size, i);
let Some(next) = self.node_ref(i).next else {
break;
};
i = next;
}
}
}
if post_compute_horizontal {
self.frame_nodes[index].size.computed_size[Axis::Horizontal as usize] = 0;
if let Some(first_child_index) = self.node_ref(index).first {
let mut node_size = self.frame_nodes[index].size.computed_size;
for child_node in NodeIter::from_index(&self.frame_nodes, first_child_index, false)
{
let child_size = child_node.size.computed_size;
match self.frame_nodes[index].size.axis {
Axis::Horizontal => {
node_size[Axis::Horizontal as usize] +=
child_size[Axis::Horizontal as usize];
}
Axis::Vertical => {
if child_size[Axis::Horizontal as usize]
> node_size[Axis::Horizontal as usize]
{
node_size[Axis::Horizontal as usize] =
child_size[Axis::Horizontal as usize];
}
}
}
}
self.frame_nodes[index].size.computed_size = node_size;
}
}
if post_compute_vertical {
self.frame_nodes[index].size.computed_size[Axis::Vertical as usize] = 0;
if let Some(first_child_index) = self.node_ref(index).first {
let mut node_size = self.frame_nodes[index].size.computed_size;
for child_node in NodeIter::from_index(&self.frame_nodes, first_child_index, false)
{
let child_size = child_node.size.computed_size;
match self.frame_nodes[index].size.axis {
Axis::Horizontal => {
if child_size[Axis::Vertical as usize]
> node_size[Axis::Vertical as usize]
{
node_size[Axis::Vertical as usize] =
child_size[Axis::Vertical as usize];
}
}
Axis::Vertical => {
node_size[Axis::Vertical as usize] +=
child_size[Axis::Vertical as usize];
}
}
}
self.frame_nodes[index].size.computed_size = node_size;
}
}
let iter = NodeIter::from_index(&self.frame_nodes, 0, true);
for node in iter { for node in iter {
let Some(persistent) = self.persistent.get_mut(&node.key) else { let Some(persistent) = self.persistent.get_mut(&node.key) else {
continue; continue;
}; };
if let Some(parent_index) = node.parent { persistent.size = node.size;
let parent_node = self.node_ref(parent_index);
} }
} }
}
struct PersistentIter<'a> {
cx: &'a Context,
node_iter: NodeIter<'a>,
}
impl<'a> PersistentIter<'a> {
fn from_context(cx: &'a Context) -> Self {
Self {
cx,
node_iter: NodeIter::from_index(&cx.frame_nodes, 0, true),
}
}
}
impl<'a> Iterator for PersistentIter<'a> {
type Item = &'a PersistentNodeData;
fn next(&mut self) -> Option<Self::Item> {
self.node_iter
.next()
.and_then(|node| self.cx.persistent.get(&node.key))
} }
} }
@ -211,14 +491,16 @@ struct NodeIter<'a> {
frame_nodes: &'a [FrameNode], frame_nodes: &'a [FrameNode],
index: NodeIndex, index: NodeIndex,
reached_end: bool, reached_end: bool,
deep: bool,
} }
impl<'a> NodeIter<'a> { impl<'a> NodeIter<'a> {
fn from_index(frame_nodes: &'a [FrameNode], index: NodeIndex) -> Self { fn from_index(frame_nodes: &'a [FrameNode], index: NodeIndex, deep: bool) -> Self {
Self { Self {
frame_nodes, frame_nodes,
index, index,
reached_end: false, reached_end: false,
deep,
} }
} }
} }
@ -232,6 +514,7 @@ impl<'a> Iterator for NodeIter<'a> {
} }
if let Some(node) = self.frame_nodes.get(self.index) { if let Some(node) = self.frame_nodes.get(self.index) {
if self.deep {
if let Some(first) = node.first { if let Some(first) = node.first {
self.index = first; self.index = first;
} else if let Some(next) = node.next { } else if let Some(next) = node.next {
@ -245,6 +528,11 @@ impl<'a> Iterator for NodeIter<'a> {
} else { } else {
self.reached_end = true; self.reached_end = true;
} }
} else if let Some(next) = node.next {
self.index = next;
} else {
self.reached_end = true;
}
return Some(node); return Some(node);
} }

View File

@ -1,9 +1,9 @@
mod editor_core; mod editor_core;
use core_graphics::{color_space::CGColorSpace, context::CGContext}; use core_graphics::{color_space::CGColorSpace, context::CGContext};
use editor_core::ui; use editor_core::ui::{self, SemanticSize};
use futures::executor::block_on; use futures::executor::block_on;
use piet_common::{kurbo::Rect, RenderContext}; use piet_common::{kurbo::Rect, RenderContext, Text, TextLayoutBuilder};
use piet_coregraphics::CoreGraphicsContext; use piet_coregraphics::CoreGraphicsContext;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::{ use winit::{
@ -286,20 +286,32 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut cx = ui::Context::new(); let mut cx = ui::Context::new();
cx.make_node("first child"); cx.make_node("first child");
cx.make_node("second child"); cx._make_node_with_semantic_size(
let key = cx.make_node("third child with children"); "second child",
[SemanticSize::Fill, SemanticSize::PercentOfParent(50)],
);
let key = cx._make_node_with_semantic_size(
"third child with children",
[SemanticSize::ChildrenSum, SemanticSize::ChildrenSum],
);
cx.push_parent(key); cx.push_parent(key);
{ {
cx.make_node("first nested child"); cx.make_node("first nested child");
cx.make_node("second nested child"); cx._make_node_with_semantic_size(
"second nested child",
[SemanticSize::FitText, SemanticSize::Exact(256)],
);
cx.make_node("third nested child"); cx.make_node("third nested child");
} }
cx.pop_parent(); cx.pop_parent();
cx.make_node("fourth child"); cx._make_node_with_semantic_size(
"SEVEN third child",
[SemanticSize::Fill, SemanticSize::FitText],
);
cx.make_node("EIGHT fifth child");
cx.debug_print(); cx.debug_print();
cx.update_layout(); cx.update_layout([window_size.width as i32, window_size.height as i32], 0);
cx.debug_print();
/*************************/ /*************************/
@ -332,6 +344,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
window.request_redraw(); window.request_redraw();
} }
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested => {
cx.update_layout([texture.width as i32, texture.height as i32], 0);
let frame = surface let frame = surface
.get_current_texture() .get_current_texture()
.expect("failed to acquire next swap chain texture"); .expect("failed to acquire next swap chain texture");
@ -370,11 +384,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
); );
piet_context.clear(None, piet_common::Color::BLACK); piet_context.clear(None, piet_common::Color::BLACK);
piet_context.fill(
Rect::new(16.0, 16.0, 256.0, 256.0), for node in cx.node_iter() {
piet_context.stroke(
Rect::new(
node.size.computed_pos[0] as f64,
(node.size.computed_pos[1]) as f64,
(node.size.computed_pos[0] + node.size.computed_size[0]) as f64,
(node.size.computed_pos[1] + node.size.computed_size[1]) as f64,
),
&piet_common::Color::WHITE, &piet_common::Color::WHITE,
2.0,
); );
let layout = piet_context
.text()
.new_text_layout(node.label.clone())
.text_color(piet_common::Color::WHITE)
.build()
.unwrap();
piet_context.draw_text(
&layout,
(
node.size.computed_pos[0] as f64,
node.size.computed_pos[1] as f64,
),
);
}
piet_context.finish()?; piet_context.finish()?;
} }