Skip to content

Commit

Permalink
#51. Switch to IdPaths.
Browse files Browse the repository at this point in the history
  • Loading branch information
wtholliday committed Aug 19, 2023
1 parent 5657fb8 commit c89a886
Show file tree
Hide file tree
Showing 36 changed files with 1,267 additions and 644 deletions.
33 changes: 20 additions & 13 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ pub struct Context {
/// Keyboard modifiers state.
pub key_mods: KeyboardModifiers,

/// The root view ID. This should be randomized for security reasons.
pub(crate) root_id: ViewId,

/// The view that has the keyboard focus.
pub(crate) focused_id: Option<ViewId>,

Expand Down Expand Up @@ -134,7 +131,6 @@ impl Context {
previous_position: [LocalPoint::zero(); 16],
mouse_button: None,
key_mods: Default::default(),
root_id: ViewId { id: 1 },
focused_id: None,
window_title: "rui".into(),
fullscreen: false,
Expand Down Expand Up @@ -168,21 +164,27 @@ impl Context {
self.window_size = window_size;
}

let mut path = vec![0];

// Run any animations.
let mut actions = vec![];
view.process(&Event::Anim, self.root_id, self, &mut actions);
view.process(&Event::Anim, &mut path, self, &mut actions);
assert!(path.len() == 1);

if self.dirty {
// Clean up state and layout.
let mut keep = vec![];
view.gc(self.root_id, self, &mut keep);
view.gc(&mut path, self, &mut keep);
assert!(path.len() == 1);
let keep_set = HashSet::<ViewId>::from_iter(keep);
self.state_map.retain(|k, _| keep_set.contains(k));
self.layout.retain(|k, _| keep_set.contains(k));

// Get a new accesskit tree.
let mut nodes = vec![];
view.access(self.root_id, self, &mut nodes);

view.access(&mut path, self, &mut nodes);
assert_eq!(path.len(), 1);

if nodes != *access_nodes {
println!("access nodes:");
Expand All @@ -201,16 +203,17 @@ impl Context {

// XXX: we're doing layout both here and in rendering.
view.layout(
self.root_id,
&mut path,
&mut LayoutArgs {
sz: [window_size.width, window_size.height].into(),
cx: self,
text_bounds: &mut |str, size, max_width| vger.text_bounds(str, size, max_width),
},
);
assert_eq!(path.len(), 1);

// Get dirty rectangles.
view.dirty(self.root_id, LocalToWorld::identity(), self);
view.dirty(&mut path, LocalToWorld::identity(), self);

self.clear_dirty();

Expand Down Expand Up @@ -244,24 +247,26 @@ impl Context {

vger.begin(window_size.width, window_size.height, scale);

let mut path = vec![0];
// Disable dirtying the state during layout and rendering
// to avoid constantly re-rendering if some state is saved.
self.enable_dirty = false;
let local_window_size = window_size.cast_unit::<LocalSpace>();
let sz = view.layout(
self.root_id,
&mut path,
&mut LayoutArgs {
sz: local_window_size,
cx: self,
text_bounds: &mut |str, size, max_width| vger.text_bounds(str, size, max_width),
},
);
assert!(path.len() == 1);

// Center the root view in the window.
self.root_offset = ((local_window_size - sz) / 2.0).into();

vger.translate(self.root_offset);
view.draw(self.root_id, &mut DrawArgs { cx: self, vger });
view.draw(&mut path, &mut DrawArgs { cx: self, vger });
self.enable_dirty = true;

if self.render_dirty {
Expand Down Expand Up @@ -305,9 +310,10 @@ impl Context {
/// Process a UI event.
pub fn process(&mut self, view: &impl View, event: &Event) {
let mut actions = vec![];
let mut path = vec![0];
view.process(
&event.offset(-self.root_offset),
self.root_id,
&mut path,
self,
&mut actions,
);
Expand All @@ -321,7 +327,8 @@ impl Context {

/// Get menu commands.
pub fn commands(&mut self, view: &impl View, cmds: &mut Vec<CommandInfo>) {
view.commands(self.root_id, self, cmds);
let mut path = vec![0];
view.commands(&mut path, self, cmds);
}

pub(crate) fn set_dirty(&mut self) {
Expand Down
16 changes: 8 additions & 8 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,29 @@ pub trait View: private::Sealed + 'static {
/// Builds an AccessKit tree. The node ID for the subtree is returned. All generated nodes are accumulated.
fn access(
&self,
_id: ViewId,
_path: &mut IdPath,
_cx: &mut Context,
_nodes: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
) -> Option<accesskit::NodeId> {
None
}

/// Accumulates information about menu bar commands.
fn commands(&self, _id: ViewId, _cx: &mut Context, _cmds: &mut Vec<CommandInfo>) {}
fn commands(&self, _path: &mut IdPath, _cx: &mut Context, _cmds: &mut Vec<CommandInfo>) {}

/// Determines dirty regions which need repainting.
fn dirty(&self, _id: ViewId, _xform: LocalToWorld, _cx: &mut Context) {}
fn dirty(&self, _path: &mut IdPath, _xform: LocalToWorld, _cx: &mut Context) {}

/// Draws the view using vger.
fn draw(&self, id: ViewId, args: &mut DrawArgs);
fn draw(&self, path: &mut IdPath, args: &mut DrawArgs);

/// Gets IDs for views currently in use.
///
/// Push onto map if the view stores layout or state info.
fn gc(&self, _id: ViewId, _cx: &mut Context, _map: &mut Vec<ViewId>) {}
fn gc(&self, _path: &mut IdPath, _cx: &mut Context, _map: &mut Vec<ViewId>) {}

/// Returns the topmost view which the point intersects.
fn hittest(&self, _id: ViewId, _pt: LocalPoint, _cx: &mut Context) -> Option<ViewId> {
fn hittest(&self, _path: &mut IdPath, _pt: LocalPoint, _cx: &mut Context) -> Option<ViewId> {
None
}

Expand All @@ -66,13 +66,13 @@ pub trait View: private::Sealed + 'static {
/// Note that we should probably have a separate text
/// sizing interface so we don't need a GPU and graphics
/// context set up to test layout.
fn layout(&self, id: ViewId, args: &mut LayoutArgs) -> LocalSize;
fn layout(&self, path: &mut IdPath, args: &mut LayoutArgs) -> LocalSize;

/// Processes an event.
fn process(
&self,
_event: &Event,
_id: ViewId,
_path: &mut IdPath,
_cx: &mut Context,
_actions: &mut Vec<Box<dyn Any>>,
) {
Expand Down
33 changes: 20 additions & 13 deletions src/viewid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,6 @@ pub struct ViewId {
}

impl ViewId {
/// Computes the ID for a child using a hashable value. For views
/// which don't have dynamic children (e.g. `vstack` etc.) the value
/// will be the integer index of the child. Dynamic
/// views (e.g. `list`) will hash an item identifier.
pub fn child<Index: Hash>(&self, index: &Index) -> Self {
let mut hasher = DefaultHasher::new();
hasher.write_u64(self.id);
index.hash(&mut hasher);
Self {
id: hasher.finish(),
}
}

/// Returns the corresponding AccessKit ID. We're assuming
/// the underlying u64 isn't zero.
pub fn access_id(&self) -> accesskit::NodeId {
Expand All @@ -33,3 +20,23 @@ impl ViewId {
self == ViewId::default()
}
}

pub type IdPath = Vec<u64>;

// Temporary function so we don't have to change too much at once.
// Just adding Id paths for now to the View functions.
pub fn hash(path: &IdPath) -> ViewId {
let mut hasher = DefaultHasher::new();
for id in path {
hasher.write_u64(*id);
}
ViewId {
id: hasher.finish(),
}
}

pub fn hh<H: Hash>(index: &H) -> u64 {
let mut hasher = DefaultHasher::new();
index.hash(&mut hasher);
hasher.finish()
}
54 changes: 37 additions & 17 deletions src/views/anim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,49 +24,69 @@ where
fn process(
&self,
event: &Event,
id: ViewId,
path: &mut IdPath,
cx: &mut Context,
actions: &mut Vec<Box<dyn Any>>,
) {
if let Event::Anim = event {
(self.func)(cx, 1.0 / 60.0) // XXX: assume 60fps for now.
}

self.child.process(event, id.child(&0), cx, actions);

path.push(0);
self.child.process(event, path, cx, actions);
path.pop();
}

fn draw(&self, id: ViewId, args: &mut DrawArgs) {
self.child.draw(id.child(&0), args);
fn draw(&self, path: &mut IdPath, args: &mut DrawArgs) {
path.push(0);
self.child.draw(path, args);
path.pop();
}

fn layout(&self, id: ViewId, args: &mut LayoutArgs) -> LocalSize {
self.child.layout(id.child(&0), args)
fn layout(&self, path: &mut IdPath, args: &mut LayoutArgs) -> LocalSize {
path.push(0);
let sz = self.child.layout(path, args);
path.pop();
sz
}

fn dirty(&self, id: ViewId, xform: LocalToWorld, cx: &mut Context) {
self.child.dirty(id.child(&0), xform, cx);
fn dirty(&self, path: &mut IdPath, xform: LocalToWorld, cx: &mut Context) {
path.push(0);
self.child.dirty(path, xform, cx);
path.pop();
}

fn hittest(&self, id: ViewId, pt: LocalPoint, cx: &mut Context) -> Option<ViewId> {
self.child.hittest(id.child(&0), pt, cx)
fn hittest(&self, path: &mut IdPath, pt: LocalPoint, cx: &mut Context) -> Option<ViewId> {
path.push(0);
let id = self.child.hittest(path, pt, cx);
path.pop();
id
}

fn commands(&self, id: ViewId, cx: &mut Context, cmds: &mut Vec<CommandInfo>) {
self.child.commands(id.child(&0), cx, cmds);
fn commands(&self, path: &mut IdPath, cx: &mut Context, cmds: &mut Vec<CommandInfo>) {
path.push(0);
self.child.commands(path, cx, cmds);
path.pop();
}

fn gc(&self, id: ViewId, cx: &mut Context, map: &mut Vec<ViewId>) {
map.push(id);
self.child.gc(id.child(&0), cx, map);
fn gc(&self, path: &mut IdPath, cx: &mut Context, map: &mut Vec<ViewId>) {
map.push(hash(path));
path.push(0);
self.child.gc(path, cx, map);
path.pop();
}

fn access(
&self,
id: ViewId,
path: &mut IdPath,
cx: &mut Context,
nodes: &mut Vec<(accesskit::NodeId, accesskit::Node)>,
) -> Option<accesskit::NodeId> {
self.child.access(id.child(&0), cx, nodes)
path.push(0);
let node_id = self.child.access(path, cx, nodes);
path.pop();
node_id
}
}

Expand Down
Loading

0 comments on commit c89a886

Please sign in to comment.