mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-30 08:00:21 +00:00
added more mouse events
This commit is contained in:
parent
a03ab3edfb
commit
7a1fdeb673
3 changed files with 319 additions and 19 deletions
102
examples/hover.rs
Normal file
102
examples/hover.rs
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
222
src/hooks.rs
222
src/hooks.rs
|
@ -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) => {
|
||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue