Skip to content

Commit

Permalink
Merge pull request #52 from audulus/id-path
Browse files Browse the repository at this point in the history
Id path
  • Loading branch information
wtholliday committed Aug 19, 2023
2 parents 5657fb8 + 50590c3 commit a9e707b
Show file tree
Hide file tree
Showing 38 changed files with 1,359 additions and 694 deletions.
25 changes: 14 additions & 11 deletions examples/counter_redux_nested.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,20 @@ fn main() {
rui(redux(AppState::new, reduce, |app_state| {
vstack((
format!("{}", app_state.count).padding(Auto),
state(|| 0, |handle, _| {
button("increment every 5 clicks", move|cx| {
cx[handle] += 1;
if cx[handle] == 5 {
cx[handle] = 0;
Action::Increment
} else {
Action::None
}
})
}),
state(
|| 0,
|handle, _| {
button("increment every 5 clicks", move |cx| {
cx[handle] += 1;
if cx[handle] == 5 {
cx[handle] = 0;
Action::Increment
} else {
Action::None
}
})
},
),
))
}));
}
10 changes: 6 additions & 4 deletions examples/counter_redux_redux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ fn main() {
rui(redux(AppState::new, reduce, |app_state| {
vstack((
format!("{}", app_state.count).padding(Auto),
redux(|| LocalState{ count: 0 }, reduce_local, |_| {
button_a("increment every 5 clicks", LocalAction::Increment)
}),
redux(
|| LocalState { count: 0 },
reduce_local,
|_| button_a("increment every 5 clicks", LocalAction::Increment),
),
))
}));
}
}
93 changes: 77 additions & 16 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ pub struct RenderInfo<'a> {
/// shouldn't have to interact with it directly.
pub struct Context {
/// Layout information for all views.
pub(crate) layout: HashMap<ViewId, LayoutBox>,
layout: HashMap<IdPath, LayoutBox>,

/// Allocated ViewIds.
view_ids: HashMap<IdPath, ViewId>,

/// Next allocated id.
next_id: ViewId,

/// Which views each touch (or mouse pointer) is interacting with.
pub(crate) touches: [ViewId; 16],
Expand All @@ -68,9 +74,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 @@ -129,12 +132,13 @@ impl Context {
pub fn new() -> Self {
Self {
layout: HashMap::new(),
view_ids: HashMap::new(),
next_id: ViewId { id: 0 },
touches: [ViewId::default(); 16],
starts: [LocalPoint::zero(); 16],
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 +172,30 @@ 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));

let mut new_layout = self.layout.clone();
new_layout.retain(|k, _| keep_set.contains(&self.view_id(k)));
self.layout = new_layout;

// 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 +214,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 +258,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 +321,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 +338,51 @@ 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 view_id(&mut self, path: &IdPath) -> ViewId {
match self.view_ids.get_mut(path) {
Some(id) => *id,
None => {
let id = self.next_id;
self.view_ids.insert(path.clone(), id);
self.next_id.id += 1;
id
}
}
}

pub(crate) fn get_layout(&self, path: &IdPath) -> LayoutBox {
match self.layout.get(path) {
Some(b) => *b,
None => LayoutBox::default(),
}
}

pub(crate) fn update_layout(&mut self, path: &IdPath, layout_box: LayoutBox) {
match self.layout.get_mut(path) {
Some(bref) => *bref = layout_box,
None => {
self.layout.insert(path.clone(), layout_box);
}
}
}

pub(crate) fn set_layout_offset(&mut self, path: &IdPath, offset: LocalOffset) {
match self.layout.get_mut(path) {
Some(boxref) => boxref.offset = offset,
None => {
self.layout.insert(
path.clone(),
LayoutBox {
rect: LocalRect::default(),
offset: offset,
},
);
}
}
}

pub(crate) fn set_dirty(&mut self) {
Expand Down Expand Up @@ -390,7 +451,7 @@ impl Context {
{
self.set_dirty();

let mut holder = self.state_map.get_mut(&id.id).unwrap();
let holder = self.state_map.get_mut(&id.id).unwrap();
holder.dirty = true;
holder.state.downcast_mut::<S>().unwrap()
}
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
21 changes: 8 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,11 @@ impl ViewId {
self == ViewId::default()
}
}

pub type IdPath = Vec<u64>;

pub fn hh<H: Hash>(index: &H) -> u64 {
let mut hasher = DefaultHasher::new();
index.hash(&mut hasher);
hasher.finish()
}
Loading

0 comments on commit a9e707b

Please sign in to comment.