added more mouse events

This commit is contained in:
Evan Almloff 2022-02-07 05:57:57 -06:00
parent a03ab3edfb
commit 7a1fdeb673
3 changed files with 319 additions and 19 deletions

102
examples/hover.rs Normal file
View file

@ -0,0 +1,102 @@
use std::{convert::TryInto, sync::Arc};
use dioxus::{events::MouseData, prelude::*};
fn main() {
rink::launch(app);
}
fn app(cx: Scope) -> Element {
fn to_str(c: &[i32; 3]) -> String {
"#".to_string() + &c.iter().map(|c| format!("{c:02X?}")).collect::<String>()
}
fn get_brightness(m: Arc<MouseData>) -> i32 {
let mb = m.buttons;
let b: i32 = m.buttons.count_ones().try_into().unwrap();
127 * b
}
let (q1_color, set_q1_color) = use_state(&cx, || [200; 3]);
let (q2_color, set_q2_color) = use_state(&cx, || [200; 3]);
let (q3_color, set_q3_color) = use_state(&cx, || [200; 3]);
let (q4_color, set_q4_color) = use_state(&cx, || [200; 3]);
let q1_color_str = to_str(q1_color);
let q2_color_str = to_str(q2_color);
let q3_color_str = to_str(q3_color);
let q4_color_str = to_str(q4_color);
cx.render(rsx! {
div {
width: "100%",
height: "100%",
flex_direction: "column",
div {
width: "100%",
height: "50%",
flex_direction: "row",
div {
border_width: "1px",
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q1_color_str}",
onmouseenter: move |m| set_q1_color([get_brightness(m.data), 0, 0]),
onmousedown: move |m| set_q1_color([get_brightness(m.data), 0, 0]),
onmouseup: move |m| set_q1_color([get_brightness(m.data), 0, 0]),
onwheel: move |w| set_q1_color([q1_color[0] + (10.0*w.delta_y) as i32, 0, 0]),
onmouseleave: move |_| set_q1_color([200; 3]),
"click me"
}
div {
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q2_color_str}",
onmouseenter: move |m| set_q2_color([get_brightness(m.data); 3]),
onmousedown: move |m| set_q2_color([get_brightness(m.data); 3]),
onmouseup: move |m| set_q2_color([get_brightness(m.data); 3]),
onwheel: move |w| set_q2_color([q2_color[0] + (10.0*w.delta_y) as i32;3]),
onmouseleave: move |_| set_q2_color([200; 3]),
"click me"
}
}
div {
width: "100%",
height: "50%",
flex_direction: "row",
div {
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q3_color_str}",
onmouseenter: move |m| set_q3_color([0, get_brightness(m.data), 0]),
onmousedown: move |m| set_q3_color([0, get_brightness(m.data), 0]),
onmouseup: move |m| set_q3_color([0, get_brightness(m.data), 0]),
onwheel: move |w| set_q3_color([0, q3_color[1] + (10.0*w.delta_y) as i32, 0]),
onmouseleave: move |_| set_q3_color([200; 3]),
"click me"
}
div {
width: "50%",
height: "100%",
justify_content: "center",
align_items: "center",
background_color: "{q4_color_str}",
onmouseenter: move |m| set_q4_color([0, 0, get_brightness(m.data)]),
onmousedown: move |m| set_q4_color([0, 0, get_brightness(m.data)]),
onmouseup: move |m| set_q4_color([0, 0, get_brightness(m.data)]),
onwheel: move |w| set_q4_color([0, 0, q4_color[2] + (10.0*w.delta_y) as i32]),
onmouseleave: move |_| set_q4_color([200; 3]),
"click me"
}
}
}
})
}

View file

@ -7,12 +7,15 @@ use dioxus_html::{on::*, KeyCode};
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
use std::{
any::Any,
borrow::BorrowMut,
cell::RefCell,
collections::{HashMap, HashSet},
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use stretch2::{prelude::Layout, Stretch};
use crate::TuiNode;
// a wrapper around the input state for easier access
// todo: fix loop
@ -108,6 +111,7 @@ impl InnerInputState {
if state.1.contains(&m.buttons) {
// if we already pressed a button and there is another button released the button crossterm sends is the button remaining
if state.1.len() > 1 {
evt.0 = "mouseup";
state.1 = vec![m.buttons];
}
// otherwise some other button was pressed. In testing it was consistantly this mapping
@ -129,7 +133,6 @@ impl InnerInputState {
}
state.0.buttons = buttons;
m.buttons = buttons;
// println!("{buttons}")
}
None => {
self.mouse = Some((
@ -151,17 +154,194 @@ impl InnerInputState {
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
.is_some();
k.repeat = repeat;
let mut new = clone_keyboard_data(k);
new.repeat = repeat;
let new = clone_keyboard_data(k);
self.last_key_pressed = Some((new, Instant::now()));
}
}
}
fn update(&mut self, evts: &mut [EventCore]) {
for e in evts {
self.apply_event(e)
fn update<'a>(
&mut self,
dom: &'a VirtualDom,
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
node: &'a VNode<'a>,
) {
struct Data<'b> {
new_pos: (i32, i32),
old_pos: Option<(i32, i32)>,
clicked: bool,
released: bool,
wheel_delta: f64,
mouse_data: &'b MouseData,
wheel_data: &'b Option<WheelData>,
};
fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
layout.location.x as i32 <= point.0
&& layout.location.x as i32 + layout.size.width as i32 >= point.0
&& layout.location.y as i32 <= point.1
&& layout.location.y as i32 + layout.size.height as i32 >= point.1
}
fn get_mouse_events<'c, 'd>(
dom: &'c VirtualDom,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
layouts: &HashMap<ElementId, TuiNode<'c>>,
node: &'c VNode<'c>,
data: &'d Data<'d>,
) -> HashSet<&'static str> {
match node {
VNode::Fragment(f) => {
let mut union = HashSet::new();
for child in f.children {
union = union
.union(&get_mouse_events(
dom,
resolved_events,
layout,
layouts,
child,
data,
))
.copied()
.collect();
}
return union;
}
VNode::Component(vcomp) => {
let idx = vcomp.scope.get().unwrap();
let new_node = dom.get_scope(idx).unwrap().root_node();
return get_mouse_events(dom, resolved_events, layout, layouts, new_node, data);
}
VNode::Placeholder(_) => return HashSet::new(),
VNode::Element(_) | VNode::Text(_) => {}
}
let id = node.try_mounted_id().unwrap();
let node = layouts.get(&id).unwrap();
let node_layout = layout.layout(node.layout).unwrap();
let previously_contained = data
.old_pos
.filter(|pos| layout_contains_point(node_layout, *pos))
.is_some();
let currently_contains = layout_contains_point(node_layout, data.new_pos);
match node.node {
VNode::Element(el) => {
let mut events = HashSet::new();
if previously_contained || currently_contains {
for c in el.children {
events = events
.union(&get_mouse_events(
dom,
resolved_events,
layout,
layouts,
c,
data,
))
.copied()
.collect();
}
}
let mut try_create_event = |name| {
// only trigger event if the event was not triggered already by a child
if events.insert(name) {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name,
element: Some(el.id.get().unwrap()),
data: Arc::new(clone_mouse_data(data.mouse_data)),
})
}
};
if currently_contains {
if !previously_contained {
try_create_event("mouseenter");
try_create_event("mouseover");
}
if data.clicked {
try_create_event("mousedown");
}
if data.released {
try_create_event("mouseup");
match data.mouse_data.button {
0 => try_create_event("click"),
2 => try_create_event("contextmenu"),
_ => (),
}
}
if let Some(w) = data.wheel_data {
if data.wheel_delta != 0.0 {
resolved_events.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: "wheel",
element: Some(el.id.get().unwrap()),
data: Arc::new(clone_wheel_data(w)),
})
}
}
} else {
if previously_contained {
try_create_event("mouseleave");
try_create_event("mouseout");
}
}
events
}
VNode::Text(_) => HashSet::new(),
_ => todo!(),
}
}
let previous_mouse = self
.mouse
.as_ref()
.map(|m| (clone_mouse_data(&m.0), m.1.clone()));
// println!("{previous_mouse:?}");
self.wheel = None;
for e in evts.iter_mut() {
self.apply_event(e);
}
// resolve hover events
if let Some(mouse) = &self.mouse {
let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
let old_pos = previous_mouse
.as_ref()
.map(|m| (m.0.screen_x, m.0.screen_y));
let clicked =
(!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
let released =
(mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
let mouse_data = &mouse.0;
let wheel_data = &self.wheel;
let data = Data {
new_pos,
old_pos,
clicked,
released,
wheel_delta,
mouse_data,
wheel_data,
};
get_mouse_events(dom, resolved_events, layout, layouts, node, &data);
}
// for s in &self.subscribers {
// s();
// }
@ -210,7 +390,13 @@ impl RinkInputHandler {
)
}
pub fn resolve_events(&self, dom: &mut VirtualDom) {
pub fn get_events<'a>(
&self,
dom: &'a VirtualDom,
layout: &Stretch,
layouts: &mut HashMap<ElementId, TuiNode<'a>>,
node: &'a VNode<'a>,
) -> Vec<UserEvent> {
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
fn inner(
queue: &Vec<(&'static str, Arc<dyn Any + Send + Sync>)>,
@ -249,25 +435,31 @@ impl RinkInputHandler {
let mut resolved_events = Vec::new();
(*self.state)
.borrow_mut()
.update(&mut (*self.queued_events).borrow_mut());
(*self.state).borrow_mut().update(
dom,
&mut (*self.queued_events).borrow_mut(),
&mut resolved_events,
layout,
layouts,
node,
);
let events: Vec<_> = self
.queued_events
.replace(Vec::new())
.into_iter()
// these events were added in the update stage
.filter(|e| !["mousedown", "mouseup", "mousemove", "drag", "wheel"].contains(&e.0))
.map(|e| (e.0, e.1.into_any()))
.collect();
inner(&events, &mut resolved_events, dom.base_scope().root_node());
inner(&events, &mut resolved_events, node);
for e in resolved_events {
dom.handle_message(SchedulerMsg::Event(e));
}
resolved_events
}
}
// translate crossterm events into dioxus events
fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
let (name, data): (&str, EventData) = match evt {
TermEvent::Key(k) => {

View file

@ -90,12 +90,10 @@ pub fn render_vdom(
terminal.clear().unwrap();
loop {
// resolve events before rendering
handler.resolve_events(vdom);
/*
-> collect all the nodes with their layout
-> solve their layout
-> resolve events
-> render the nodes in the right place with tui/crosstream
-> while rendering, apply styling
@ -111,10 +109,11 @@ pub fn render_vdom(
let root_node = vdom.base_scope().root_node();
layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
/*
Compute the layout given th terminal size
Compute the layout given the terminal size
*/
let node_id = root_node.try_mounted_id().unwrap();
let root_layout = nodes[&node_id].layout;
let mut events = Vec::new();
terminal.draw(|frame| {
// size is guaranteed to not change when rendering
@ -130,10 +129,17 @@ pub fn render_vdom(
},
)
.unwrap();
// resolve events before rendering
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
render::render_vnode(frame, &layout, &mut nodes, vdom, root_node);
assert!(nodes.is_empty());
})?;
for e in events {
vdom.handle_message(SchedulerMsg::Event(e));
}
use futures::future::{select, Either};
{
let wait = vdom.wait_for_work();