Skip to content

Commit

Permalink
First pass at making a scroll handling component; wire up to fader
Browse files Browse the repository at this point in the history
Many open questions:

* Delta direction may vary based on "natural scroll" setting in OS?
* Does it work on trackpads or do we need to use `scroll` event instead?
* There are different delta modes (pixels vs lines etc)
* Actual delta seems fixed in increments of 100 but this may be specific
  to my OS / browser
* I am putting the direction in their own enum variant (instead of
  negative) but given we have to use a negative number later, this might
  not be useful.
* There are other event attributes that web_sys doesn't expose which may
  have more complete data for making sure the way we interpret scroll
  deltas is more normalised.

Ideally, we could detect the minimum scroll increment and pass a single
Up/Down scroll increment internally. Right now I'm passing 100 or -100 to the
fader and then scaling that down by 0.001 but that doesn't feel right.
  • Loading branch information
bjeanes committed Apr 8, 2020
1 parent d176293 commit 2c79587
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 13 deletions.
1 change: 1 addition & 0 deletions frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ web-sys = { version = "0.3", features = [
"CssStyleDeclaration",
"HtmlCanvasElement",
"InputEvent",
"WheelEvent",
"WebSocket",
] }

Expand Down
1 change: 1 addition & 0 deletions frontend/src/component/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod drag_target;
pub mod scroll_target;
97 changes: 97 additions & 0 deletions frontend/src/component/scroll_target.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use gloo_events::{EventListener, EventListenerOptions};
use wasm_bindgen::JsCast;
use web_sys::{WheelEvent, Element};
use yew::{html, Component, ComponentLink, Html, ShouldRender, Properties, NodeRef, Children, Callback, Renderable};

#[derive(Properties, Clone)]
pub struct ScrollProps {
#[prop_or_default]
pub on_scroll: Option<Callback<Scroll>>,

#[prop_or_default]
pub children: Children,
}

#[derive(Debug, Clone)]
pub enum Scroll {
Up(f64),
Down(f64),
}

pub struct ScrollTarget {
link: ComponentLink<Self>,
props: ScrollProps,
container: NodeRef,
// state: Option<ScrollState>,
}

impl Component for ScrollTarget {
type Properties = ScrollProps;
type Message = ();

fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
ScrollTarget {
link,
props,
container: NodeRef::default(),
}
}

fn change(&mut self, props: Self::Properties) -> ShouldRender {
self.props = props;
true
}

fn update(&mut self, msg: Self::Message) -> ShouldRender {
crate::log!("{:?}", msg);
false
}

// yew doesn't know about `onwheel` and tries to call `to_string()`, so
// attaching event handler manually.
fn mounted(&mut self) -> ShouldRender {
if let Some(el) = self.container.cast::<Element>() {
let options = EventListenerOptions::enable_prevent_default();
let wheel = EventListener::new_with_options(
&el, "wheel", options,
{
let on_scroll = self.props.on_scroll.clone();
move |ev| {
if let Some(on_scroll) = &on_scroll {
if let Some(ev) = ev.dyn_ref::<WheelEvent>().cloned() {
let delta = ev.delta_y();

let scroll = if delta < 0.0 {
Scroll::Up(delta.abs())
} else {
Scroll::Down(delta.abs())
};

ev.prevent_default();
ev.stop_propagation();
on_scroll.emit(scroll);
}
}
}
}
);

// TODO: maybe store this in the state. Unclear when this is dropped
// if forgotten.
wheel.forget();
}

false
}

fn view(&self) -> Html {
html! {
<div
class="scroll-target-container"
ref={self.container.clone()}
>
{self.props.children.render()}
</div>
}
}
}
43 changes: 30 additions & 13 deletions frontend/src/control/fader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use web_sys::{HtmlCanvasElement, CanvasRenderingContext2d, MouseEvent};
use yew::{html, Component, ComponentLink, Html, ShouldRender, Properties, NodeRef, Callback};

use crate::component::drag_target::{DragTarget, DragEvent};
use crate::component::scroll_target::{Scroll, ScrollTarget};
use crate::util;

const FADER_WIDTH: usize = 64;
Expand Down Expand Up @@ -43,6 +44,7 @@ pub enum FaderMsg {
DragStart(DragEvent),
Drag(DragEvent),
DragEnd(DragEvent),
Scroll(Scroll)
}

impl Fader {
Expand Down Expand Up @@ -142,6 +144,16 @@ impl Component for Fader {
self.mouse_mode = MouseMode::Normal;
true
}
FaderMsg::Scroll(dir) => {
let delta = match dir {
Scroll::Up(delta) => delta,
Scroll::Down(delta) => delta * -1.0,
};
let factor = 0.0001;
let fader_value = util::clamp(0.0, 1.0, self.props.value + delta * factor);
self.props.onchange.emit(fader_value);
true
}
}
}

Expand Down Expand Up @@ -196,24 +208,29 @@ impl Component for Fader {

let canvas_style = match self.mouse_mode {
MouseMode::Normal => "",
MouseMode::Hover | MouseMode::Drag { .. } => "cursor:pointer;"
MouseMode::Hover => "cursor:grab;",
MouseMode::Drag { .. } => "cursor:grabbing;"
};

html! {
<div class="control-fader">
<DragTarget
on_drag_start={self.link.callback(FaderMsg::DragStart)}
on_drag={self.link.callback(FaderMsg::Drag)}
on_drag_end={self.link.callback(FaderMsg::DragEnd)}
<ScrollTarget
on_scroll={self.link.callback(FaderMsg::Scroll)}
>
<canvas
width={FADER_WIDTH}
height={FADER_HEIGHT}
ref={self.canvas.clone()}
style={canvas_style}
onmousemove={self.link.callback(FaderMsg::MouseMove)}
/>
</DragTarget>
<DragTarget
on_drag_start={self.link.callback(FaderMsg::DragStart)}
on_drag={self.link.callback(FaderMsg::Drag)}
on_drag_end={self.link.callback(FaderMsg::DragEnd)}
>
<canvas
width={FADER_WIDTH}
height={FADER_HEIGHT}
ref={self.canvas.clone()}
style={canvas_style}
onmousemove={self.link.callback(FaderMsg::MouseMove)}
/>
</DragTarget>
</ScrollTarget>
</div>
}
}
Expand Down

0 comments on commit 2c79587

Please sign in to comment.