mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-18 22:58:30 +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 futures::{channel::mpsc::UnboundedReceiver, StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
borrow::BorrowMut,
|
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
use stretch2::{prelude::Layout, Stretch};
|
||||||
|
|
||||||
|
use crate::TuiNode;
|
||||||
|
|
||||||
// a wrapper around the input state for easier access
|
// a wrapper around the input state for easier access
|
||||||
// todo: fix loop
|
// todo: fix loop
|
||||||
|
@ -108,6 +111,7 @@ impl InnerInputState {
|
||||||
if state.1.contains(&m.buttons) {
|
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 we already pressed a button and there is another button released the button crossterm sends is the button remaining
|
||||||
if state.1.len() > 1 {
|
if state.1.len() > 1 {
|
||||||
|
evt.0 = "mouseup";
|
||||||
state.1 = vec![m.buttons];
|
state.1 = vec![m.buttons];
|
||||||
}
|
}
|
||||||
// otherwise some other button was pressed. In testing it was consistantly this mapping
|
// otherwise some other button was pressed. In testing it was consistantly this mapping
|
||||||
|
@ -129,7 +133,6 @@ impl InnerInputState {
|
||||||
}
|
}
|
||||||
state.0.buttons = buttons;
|
state.0.buttons = buttons;
|
||||||
m.buttons = buttons;
|
m.buttons = buttons;
|
||||||
// println!("{buttons}")
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
self.mouse = Some((
|
self.mouse = Some((
|
||||||
|
@ -151,17 +154,194 @@ impl InnerInputState {
|
||||||
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
|
.filter(|k2| k2.0.key == k.key && k2.1.elapsed() < MAX_REPEAT_TIME)
|
||||||
.is_some();
|
.is_some();
|
||||||
k.repeat = repeat;
|
k.repeat = repeat;
|
||||||
let mut new = clone_keyboard_data(k);
|
let new = clone_keyboard_data(k);
|
||||||
new.repeat = repeat;
|
|
||||||
self.last_key_pressed = Some((new, Instant::now()));
|
self.last_key_pressed = Some((new, Instant::now()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, evts: &mut [EventCore]) {
|
fn update<'a>(
|
||||||
for e in evts {
|
&mut self,
|
||||||
self.apply_event(e)
|
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 {
|
// for s in &self.subscribers {
|
||||||
// s();
|
// 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
|
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
|
||||||
fn inner(
|
fn inner(
|
||||||
queue: &Vec<(&'static str, Arc<dyn Any + Send + Sync>)>,
|
queue: &Vec<(&'static str, Arc<dyn Any + Send + Sync>)>,
|
||||||
|
@ -249,25 +435,31 @@ impl RinkInputHandler {
|
||||||
|
|
||||||
let mut resolved_events = Vec::new();
|
let mut resolved_events = Vec::new();
|
||||||
|
|
||||||
(*self.state)
|
(*self.state).borrow_mut().update(
|
||||||
.borrow_mut()
|
dom,
|
||||||
.update(&mut (*self.queued_events).borrow_mut());
|
&mut (*self.queued_events).borrow_mut(),
|
||||||
|
&mut resolved_events,
|
||||||
|
layout,
|
||||||
|
layouts,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
|
||||||
let events: Vec<_> = self
|
let events: Vec<_> = self
|
||||||
.queued_events
|
.queued_events
|
||||||
.replace(Vec::new())
|
.replace(Vec::new())
|
||||||
.into_iter()
|
.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()))
|
.map(|e| (e.0, e.1.into_any()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
inner(&events, &mut resolved_events, dom.base_scope().root_node());
|
inner(&events, &mut resolved_events, node);
|
||||||
|
|
||||||
for e in resolved_events {
|
resolved_events
|
||||||
dom.handle_message(SchedulerMsg::Event(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// translate crossterm events into dioxus events
|
||||||
fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
|
||||||
let (name, data): (&str, EventData) = match evt {
|
let (name, data): (&str, EventData) = match evt {
|
||||||
TermEvent::Key(k) => {
|
TermEvent::Key(k) => {
|
||||||
|
|
14
src/lib.rs
14
src/lib.rs
|
@ -90,12 +90,10 @@ pub fn render_vdom(
|
||||||
terminal.clear().unwrap();
|
terminal.clear().unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// resolve events before rendering
|
|
||||||
handler.resolve_events(vdom);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
-> collect all the nodes with their layout
|
-> collect all the nodes with their layout
|
||||||
-> solve their layout
|
-> solve their layout
|
||||||
|
-> resolve events
|
||||||
-> render the nodes in the right place with tui/crosstream
|
-> render the nodes in the right place with tui/crosstream
|
||||||
-> while rendering, apply styling
|
-> while rendering, apply styling
|
||||||
|
|
||||||
|
@ -111,10 +109,11 @@ pub fn render_vdom(
|
||||||
let root_node = vdom.base_scope().root_node();
|
let root_node = vdom.base_scope().root_node();
|
||||||
layout::collect_layout(&mut layout, &mut nodes, vdom, 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 node_id = root_node.try_mounted_id().unwrap();
|
||||||
let root_layout = nodes[&node_id].layout;
|
let root_layout = nodes[&node_id].layout;
|
||||||
|
let mut events = Vec::new();
|
||||||
|
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
// size is guaranteed to not change when rendering
|
// size is guaranteed to not change when rendering
|
||||||
|
@ -130,10 +129,17 @@ pub fn render_vdom(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.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);
|
render::render_vnode(frame, &layout, &mut nodes, vdom, root_node);
|
||||||
assert!(nodes.is_empty());
|
assert!(nodes.is_empty());
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
for e in events {
|
||||||
|
vdom.handle_message(SchedulerMsg::Event(e));
|
||||||
|
}
|
||||||
|
|
||||||
use futures::future::{select, Either};
|
use futures::future::{select, Either};
|
||||||
{
|
{
|
||||||
let wait = vdom.wait_for_work();
|
let wait = vdom.wait_for_work();
|
||||||
|
|
Loading…
Add table
Reference in a new issue