added html native events

This commit is contained in:
Evan Almloff committed 2022-02-05 16:28:19 -06:00
1 parent dd60c9ce3c
commit 03ff72dddc
4 files changed
+450 -113

No files matched your search

+1
View File
@@ -11,6 +11,7 @@ crossterm = "0.22.1"
anyhow = "1.0.42"
thiserror = "1.0.24"
dioxus = "0.1.8"
dioxus-html = "0.1.6"
hecs = "0.7.3"
ctrlc = "3.2.1"
bumpalo = { version = "3.8.0", features = ["boxed"] }
+27 -24
View File
@@ -1,15 +1,17 @@
use crossterm::event::{KeyCode, KeyEvent, MouseEvent};
use dioxus::prelude::*;
use dioxus_html::on::{KeyboardEvent, MouseEvent};
use dioxus_html::KeyCode;
fn main() {
rink::launch(app);
}
fn app(cx: Scope) -> Element {
let (key, set_key) = use_state(&cx, || KeyCode::Null);
let (key, set_key) = use_state(&cx, || KeyCode::Space);
let (mouse, set_mouse) = use_state(&cx, || (0, 0));
let (size, set_size) = use_state(&cx, || (0, 0));
let (count, set_count) = use_state(&cx, || 0);
let (buttons, set_buttons) = use_state(&cx, || 0);
let (mouse_clicked, set_mouse_clicked) = use_state(&cx, || false);
cx.render(rsx! {
div {
@@ -19,30 +21,31 @@ fn app(cx: Scope) -> Element {
justify_content: "center",
align_items: "center",
flex_direction: "column",
rink::InputHandler {
onkeydown: move |evt: KeyEvent| {
use crossterm::event::KeyCode::*;
match evt.code {
Left => set_count(count + 1),
Right => set_count(count - 1),
Up => set_count(count + 10),
Down => set_count(count - 10),
_ => {},
}
set_key(evt.code);
},
onmousedown: move |evt: MouseEvent| {
set_mouse((evt.row, evt.column));
},
onresize: move |dims| {
set_size(dims);
},
onkeydown: move |evt: KeyboardEvent| {
match evt.data.key_code {
KeyCode::LeftArrow => set_count(count + 1),
KeyCode::RightArrow => set_count(count - 1),
KeyCode::UpArrow => set_count(count + 10),
KeyCode::DownArrow => set_count(count - 10),
_ => {},
}
set_key(evt.key_code);
},
onmousedown: move |evt: MouseEvent| {
set_mouse((evt.data.screen_x, evt.data.screen_y));
set_buttons(evt.data.buttons);
set_mouse_clicked(true);
},
onmouseup: move |evt: MouseEvent| {
set_buttons(evt.data.buttons);
set_mouse_clicked(false);
},
"count: {count:?}",
"key: {key:?}",
"mouse: {mouse:?}",
"resize: {size:?}",
"mouse buttons: {buttons:b}",
"mouse pos: {mouse:?}",
"mouse button pressed: {mouse_clicked}"
}
})
}
+410 -86
View File
@@ -1,109 +1,433 @@
use crossterm::event::{Event as TermEvent, KeyEvent, MouseEvent};
use crossterm::event::{
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
};
use dioxus::core::*;
use dioxus::prelude::Props;
use dioxus_html::{on::*, KeyCode};
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
use std::{
cell::{Cell, RefCell},
collections::HashMap,
any::Any,
borrow::BorrowMut,
cell::RefCell,
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
pub struct RinkContext {
last_event: Rc<Cell<Option<TermEvent>>>,
subscribers: Rc<RefCell<HashMap<ScopeId, bool>>>,
// a wrapper around the input state for easier access
// todo: fix loop
// pub struct InputState(Rc<Rc<RefCell<InnerInputState>>>);
// impl InputState {
// pub fn get(cx: &ScopeState) -> InputState {
// let inner = cx
// .consume_context::<Rc<RefCell<InnerInputState>>>()
// .expect("Rink InputState can only be used in Rink apps!");
// (**inner).borrow_mut().subscribe(cx.schedule_update());
// InputState(inner)
// }
// pub fn mouse(&self) -> Option<MouseData> {
// let data = (**self.0).borrow();
// data.mouse.as_ref().map(|m| clone_mouse_data(m))
// }
// pub fn wheel(&self) -> Option<WheelData> {
// let data = (**self.0).borrow();
// data.wheel.as_ref().map(|w| clone_wheel_data(w))
// }
// pub fn screen(&self) -> Option<(u16, u16)> {
// let data = (**self.0).borrow();
// data.screen.as_ref().map(|m| m.clone())
// }
// pub fn last_key_pressed(&self) -> Option<KeyboardData> {
// let data = (**self.0).borrow();
// data.last_key_pressed
// .as_ref()
// .map(|k| clone_keyboard_data(&k.0))
// }
// }
type EventCore = (&'static str, EventData);
#[derive(Debug)]
enum EventData {
Mouse(MouseData),
Wheel(WheelData),
Screen((u16, u16)),
Keyboard(KeyboardData),
}
impl EventData {
fn into_any(self) -> Arc<dyn Any + Send + Sync> {
match self {
Self::Mouse(m) => Arc::new(m),
Self::Wheel(w) => Arc::new(w),
Self::Screen(s) => Arc::new(s),
Self::Keyboard(k) => Arc::new(k),
}
}
}
impl RinkContext {
pub fn new(mut receiver: UnboundedReceiver<TermEvent>, cx: &ScopeState) -> Self {
let updater = cx.schedule_update_any();
let last_event = Rc::new(Cell::new(None));
let last_event2 = last_event.clone();
let subscribers = Rc::new(RefCell::new(HashMap::new()));
let subscribers2 = subscribers.clone();
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
pub struct InnerInputState {
mouse: Option<MouseData>,
wheel: Option<WheelData>,
last_key_pressed: Option<(KeyboardData, Instant)>,
screen: Option<(u16, u16)>,
// subscribers: Vec<Rc<dyn Fn() + 'static>>,
}
impl InnerInputState {
fn new() -> Self {
Self {
mouse: None,
wheel: None,
last_key_pressed: None,
screen: None,
// subscribers: Vec::new(),
}
}
// stores current input state and transforms events based on that state
fn apply_event(&mut self, evt: &mut EventCore) {
match evt.1 {
EventData::Mouse(ref mut m) => match &mut self.mouse {
Some(state) => {
*state = clone_mouse_data(m);
// crossterm always outputs the left mouse button on mouse up
// let mut buttons = state.buttons;
// *state = clone_mouse_data(m);
// match evt.0 {
// "mouseup" => {
// buttons &= !m.buttons;
// }
// "mousedown" => {
// buttons |= m.buttons;
// }
// _ => (),
// }
// state.buttons = buttons;
// m.buttons = buttons;
}
None => {
self.mouse = Some(clone_mouse_data(m));
}
},
EventData::Wheel(ref w) => self.wheel = Some(clone_wheel_data(w)),
EventData::Screen(ref s) => self.screen = Some(s.clone()),
EventData::Keyboard(ref mut k) => {
let repeat = self
.last_key_pressed
.as_ref()
.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;
self.last_key_pressed = Some((new, Instant::now()));
}
}
}
fn update(&mut self, evts: &mut [EventCore]) {
for e in evts {
self.apply_event(e)
}
// for s in &self.subscribers {
// s();
// }
}
// fn subscribe(&mut self, f: Rc<dyn Fn() + 'static>) {
// self.subscribers.push(f)
// }
}
pub struct RinkInputHandler {
state: Rc<RefCell<InnerInputState>>,
queued_events: Rc<RefCell<Vec<EventCore>>>,
}
impl RinkInputHandler {
/// global context that handles events
/// limitations: GUI key modifier is never detected
pub fn new(
mut receiver: UnboundedReceiver<TermEvent>,
cx: &ScopeState,
) -> (Self, Rc<RefCell<InnerInputState>>) {
let queued_events = Rc::new(RefCell::new(Vec::new()));
let queued_events2 = Rc::<RefCell<std::vec::Vec<_>>>::downgrade(&queued_events);
cx.push_future(async move {
while let Some(evt) = receiver.next().await {
last_event2.replace(Some(evt));
for (subscriber, received) in subscribers2.borrow_mut().iter_mut() {
updater(*subscriber);
*received = false;
if let Some(evt) = get_event(evt) {
if let Some(v) = queued_events2.upgrade() {
(*v).borrow_mut().push(evt);
} else {
break;
}
}
}
});
Self {
last_event,
subscribers,
}
let state = Rc::new(RefCell::new(InnerInputState::new()));
(
Self {
state: state.clone(),
queued_events,
},
state,
)
}
pub fn subscribe_to_events(&self, scope: ScopeId) {
self.subscribers.borrow_mut().insert(scope, false);
}
pub fn get_event(&self, scope: ScopeId) -> Option<TermEvent> {
let mut subscribers = self.subscribers.borrow_mut();
let received = subscribers.get_mut(&scope)?;
if !*received {
*received = true;
self.last_event.get()
} else {
None
}
}
}
#[derive(Props)]
pub struct AppHandlerProps<'a> {
#[props(default)]
onkeydown: EventHandler<'a, KeyEvent>,
#[props(default)]
onmousedown: EventHandler<'a, MouseEvent>,
#[props(default)]
onresize: EventHandler<'a, (u16, u16)>,
}
/// This component lets you handle input events
///
/// Once attached to the DOM, it will listen for input events from the terminal
///
///
pub fn InputHandler<'a>(cx: Scope<'a, AppHandlerProps<'a>>) -> Element {
let rcx = cx.use_hook(|_| {
let rcx = cx
.consume_context::<RinkContext>()
.unwrap_or_else(|| panic!("Rink InputHandlers can only be used in Rink apps!"));
// our component will only re-render if new events are received ... or if the parent is updated
// todo: if update was not caused by a new event, we should not re-render
// perhaps add some tracking to context?
rcx.subscribe_to_events(cx.scope_id());
rcx
});
{
if let Some(evet) = rcx.get_event(cx.scope_id()) {
match evet {
TermEvent::Key(key) => {
cx.props.onkeydown.call(key.clone());
// let mut handler = cx.props.keydown.borrow_mut();
// handler(*key);
// if let Some(handler) = cx.props.onkeydown {
// handler(*key);
// }
pub fn resolve_events(&self, dom: &mut VirtualDom) {
// 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>)>,
resolved: &mut Vec<UserEvent>,
node: &VNode,
) {
match node {
VNode::Fragment(frag) => {
for c in frag.children {
inner(queue, resolved, c);
}
}
TermEvent::Mouse(mouse) => {
cx.props.onmousedown.call(mouse.clone());
}
TermEvent::Resize(x, y) => {
cx.props.onresize.call((x, y));
VNode::Element(el) => {
for l in el.listeners {
for (name, data) in queue.iter() {
if *name == l.event {
if let Some(id) = el.id.get() {
resolved.push(UserEvent {
scope_id: None,
priority: EventPriority::Medium,
name: *name,
element: Some(id),
data: data.clone(),
});
}
}
}
}
for c in el.children {
inner(queue, resolved, c);
}
}
_ => (),
}
}
}
None
let mut resolved_events = Vec::new();
(*self.state)
.borrow_mut()
.update(&mut (*self.queued_events).borrow_mut());
let events: Vec<_> = self
.queued_events
.replace(Vec::new())
.into_iter()
.map(|e| (e.0, e.1.into_any()))
.collect();
inner(&events, &mut resolved_events, dom.base_scope().root_node());
for e in resolved_events {
dom.handle_message(SchedulerMsg::Event(e));
}
}
}
fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
let (name, data): (&str, EventData) = match evt {
TermEvent::Key(k) => {
let key = translate_key_code(k.code)?;
(
"keydown",
// from https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
EventData::Keyboard(KeyboardData {
char_code: key.raw_code(),
key: format!("{key:?}"),
key_code: key,
alt_key: k.modifiers.contains(KeyModifiers::ALT),
ctrl_key: k.modifiers.contains(KeyModifiers::CONTROL),
meta_key: false,
shift_key: k.modifiers.contains(KeyModifiers::SHIFT),
locale: Default::default(),
location: 0x00,
repeat: Default::default(),
which: Default::default(),
}),
)
}
TermEvent::Mouse(m) => {
let (x, y) = (m.column.into(), m.row.into());
let alt = m.modifiers.contains(KeyModifiers::ALT);
let shift = m.modifiers.contains(KeyModifiers::SHIFT);
let ctrl = m.modifiers.contains(KeyModifiers::CONTROL);
let meta = false;
let get_mouse_data = |b| {
let buttons = match b {
None => 0,
Some(MouseButton::Left) => 1,
Some(MouseButton::Right) => 2,
Some(MouseButton::Middle) => 4,
};
let button_state = match b {
None => 0,
Some(MouseButton::Left) => 0,
Some(MouseButton::Middle) => 1,
Some(MouseButton::Right) => 2,
};
// from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
EventData::Mouse(MouseData {
alt_key: alt,
button: button_state,
buttons,
client_x: x,
client_y: y,
ctrl_key: ctrl,
meta_key: meta,
page_x: x,
page_y: y,
screen_x: x,
screen_y: y,
shift_key: shift,
})
};
let get_wheel_data = |up| {
// from https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent
EventData::Wheel(WheelData {
delta_mode: 0x01,
delta_x: 0.0,
delta_y: if up { -1.0 } else { 1.0 },
delta_z: 0.0,
})
};
match m.kind {
MouseEventKind::Down(b) => ("mousedown", get_mouse_data(Some(b))),
MouseEventKind::Up(b) => ("mouseup", get_mouse_data(Some(b))),
MouseEventKind::Drag(b) => ("drag", get_mouse_data(Some(b))),
MouseEventKind::Moved => ("mousemove", get_mouse_data(None)),
MouseEventKind::ScrollDown => ("scroll", get_wheel_data(false)),
MouseEventKind::ScrollUp => ("scroll", get_wheel_data(true)),
}
}
TermEvent::Resize(x, y) => ("resize", EventData::Screen((x, y))),
};
Some((name, data))
}
fn translate_key_code(c: TermKeyCode) -> Option<KeyCode> {
match c {
TermKeyCode::Backspace => Some(KeyCode::Backspace),
TermKeyCode::Enter => Some(KeyCode::Enter),
TermKeyCode::Left => Some(KeyCode::LeftArrow),
TermKeyCode::Right => Some(KeyCode::RightArrow),
TermKeyCode::Up => Some(KeyCode::UpArrow),
TermKeyCode::Down => Some(KeyCode::DownArrow),
TermKeyCode::Home => Some(KeyCode::Home),
TermKeyCode::End => Some(KeyCode::End),
TermKeyCode::PageUp => Some(KeyCode::PageUp),
TermKeyCode::PageDown => Some(KeyCode::PageDown),
TermKeyCode::Tab => Some(KeyCode::Tab),
TermKeyCode::BackTab => None,
TermKeyCode::Delete => Some(KeyCode::Delete),
TermKeyCode::Insert => Some(KeyCode::Insert),
TermKeyCode::F(fn_num) => match fn_num {
1 => Some(KeyCode::F1),
2 => Some(KeyCode::F2),
3 => Some(KeyCode::F3),
4 => Some(KeyCode::F4),
5 => Some(KeyCode::F5),
6 => Some(KeyCode::F6),
7 => Some(KeyCode::F7),
8 => Some(KeyCode::F8),
9 => Some(KeyCode::F9),
10 => Some(KeyCode::F10),
11 => Some(KeyCode::F11),
12 => Some(KeyCode::F12),
_ => None,
},
TermKeyCode::Char(c) => match c.to_uppercase().next().unwrap() {
'A' => Some(KeyCode::A),
'B' => Some(KeyCode::B),
'C' => Some(KeyCode::C),
'D' => Some(KeyCode::D),
'E' => Some(KeyCode::E),
'F' => Some(KeyCode::F),
'G' => Some(KeyCode::G),
'H' => Some(KeyCode::H),
'I' => Some(KeyCode::I),
'J' => Some(KeyCode::J),
'K' => Some(KeyCode::K),
'L' => Some(KeyCode::L),
'M' => Some(KeyCode::M),
'N' => Some(KeyCode::N),
'O' => Some(KeyCode::O),
'P' => Some(KeyCode::P),
'Q' => Some(KeyCode::Q),
'R' => Some(KeyCode::R),
'S' => Some(KeyCode::S),
'T' => Some(KeyCode::T),
'U' => Some(KeyCode::U),
'V' => Some(KeyCode::V),
'W' => Some(KeyCode::W),
'X' => Some(KeyCode::X),
'Y' => Some(KeyCode::Y),
'Z' => Some(KeyCode::Z),
_ => None,
},
TermKeyCode::Null => None,
TermKeyCode::Esc => Some(KeyCode::Escape),
}
}
fn clone_mouse_data(m: &MouseData) -> MouseData {
MouseData {
client_x: m.client_x,
client_y: m.client_y,
page_x: m.page_x,
page_y: m.page_y,
screen_x: m.screen_x,
screen_y: m.screen_y,
alt_key: m.alt_key,
ctrl_key: m.ctrl_key,
meta_key: m.meta_key,
shift_key: m.shift_key,
button: m.button,
buttons: m.buttons,
}
}
fn clone_keyboard_data(k: &KeyboardData) -> KeyboardData {
KeyboardData {
char_code: k.char_code,
key: k.key.clone(),
key_code: k.key_code,
alt_key: k.alt_key,
ctrl_key: k.ctrl_key,
meta_key: k.meta_key,
shift_key: k.shift_key,
locale: k.locale.clone(),
location: k.location,
repeat: k.repeat,
which: k.which,
}
}
fn clone_wheel_data(w: &WheelData) -> WheelData {
WheelData {
delta_mode: w.delta_mode,
delta_x: w.delta_x,
delta_y: w.delta_y,
delta_z: w.delta_x,
}
}
+12 -3
View File
@@ -31,11 +31,13 @@ pub fn launch(app: Component<()>) {
let cx = dom.base_scope();
cx.provide_root_context(RinkContext::new(rx, cx));
let (handler, state) = RinkInputHandler::new(rx, cx);
cx.provide_root_context(state);
dom.rebuild();
render_vdom(&mut dom, tx).unwrap();
render_vdom(&mut dom, tx, handler).unwrap();
}
pub struct TuiNode<'a> {
@@ -44,7 +46,11 @@ pub struct TuiNode<'a> {
pub node: &'a VNode<'a>,
}
pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Result<()> {
pub fn render_vdom(
vdom: &mut VirtualDom,
ctx: UnboundedSender<TermEvent>,
handler: RinkInputHandler,
) -> Result<()> {
// Setup input handling
let (tx, mut rx) = unbounded();
std::thread::spawn(move || {
@@ -84,6 +90,9 @@ pub fn render_vdom(vdom: &mut VirtualDom, ctx: UnboundedSender<TermEvent>) -> Re
terminal.clear().unwrap();
loop {
// resolve events before rendering
handler.resolve_events(vdom);
/*
-> collect all the nodes with their layout
-> solve their layout