support horizontal scrolling

This commit is contained in:
Dustin Carlino 2020-01-14 15:47:35 -08:00
parent 6db84efdae
commit d637243f2a
6 changed files with 140 additions and 85 deletions

View File

@ -86,7 +86,7 @@ impl Canvas {
self.drag_canvas_from = Some(self.get_cursor_in_screen_space()); self.drag_canvas_from = Some(self.get_cursor_in_screen_space());
} }
if let Some(scroll) = input.get_mouse_scroll() { if let Some((_, scroll)) = input.get_mouse_scroll() {
let old_zoom = self.cam_zoom; let old_zoom = self.cam_zoom;
// By popular request, some limits ;) // By popular request, some limits ;)
self.cam_zoom = 1.1_f64 self.cam_zoom = 1.1_f64

View File

@ -19,8 +19,7 @@ pub enum Event {
MouseMovedTo(ScreenPt), MouseMovedTo(ScreenPt),
WindowLostCursor, WindowLostCursor,
WindowGainedCursor, WindowGainedCursor,
// Vertical only MouseWheelScroll(f64, f64),
MouseWheelScroll(f64),
WindowResized(f64, f64), WindowResized(f64, f64),
} }
@ -58,18 +57,18 @@ impl Event {
Some(Event::MouseMovedTo(ScreenPt::new(pos.x, pos.y))) Some(Event::MouseMovedTo(ScreenPt::new(pos.x, pos.y)))
} }
glutin::WindowEvent::MouseWheel { delta, .. } => match delta { glutin::WindowEvent::MouseWheel { delta, .. } => match delta {
glutin::MouseScrollDelta::LineDelta(_, dy) => { glutin::MouseScrollDelta::LineDelta(dx, dy) => {
if dy == 0.0 { if dx == 0.0 && dy == 0.0 {
None None
} else { } else {
Some(Event::MouseWheelScroll(f64::from(dy))) Some(Event::MouseWheelScroll(f64::from(dx), f64::from(dy)))
} }
} }
// This one only happens on Mac. The scrolling is way too fast, so slow it down. // This one only happens on Mac. The scrolling is way too fast, so slow it down.
// Probably the better way is to convert the LogicalPosition to a PhysicalPosition // Probably the better way is to convert the LogicalPosition to a PhysicalPosition
// somehow knowing the DPI. // somehow knowing the DPI.
glutin::MouseScrollDelta::PixelDelta(pos) => { glutin::MouseScrollDelta::PixelDelta(pos) => {
Some(Event::MouseWheelScroll(0.1 * pos.y)) Some(Event::MouseWheelScroll(0.1 * pos.x, 0.1 * pos.y))
} }
}, },
glutin::WindowEvent::Resized(size) => { glutin::WindowEvent::Resized(size) => {

View File

@ -62,7 +62,11 @@ impl<'a> EventCtx<'a> {
self.fake_mouseover self.fake_mouseover
|| self.input.window_lost_cursor() || self.input.window_lost_cursor()
|| (!self.is_dragging() && self.input.get_moved_mouse().is_some()) || (!self.is_dragging() && self.input.get_moved_mouse().is_some())
|| self.input.get_mouse_scroll().is_some() || self
.input
.get_mouse_scroll()
.map(|(_, dy)| dy != 0.0)
.unwrap_or(false)
} }
pub fn normal_left_click(&mut self) -> bool { pub fn normal_left_click(&mut self) -> bool {

View File

@ -109,9 +109,9 @@ impl UserInput {
None None
} }
pub fn get_mouse_scroll(&self) -> Option<f64> { pub(crate) fn get_mouse_scroll(&self) -> Option<(f64, f64)> {
if let Event::MouseWheelScroll(dy) = self.event { if let Event::MouseWheelScroll(dx, dy) = self.event {
return Some(dy); return Some((dx, dy));
} }
None None
} }

View File

@ -363,7 +363,7 @@ impl ManagedWidget {
nodes: &mut Vec<Node>, nodes: &mut Vec<Node>,
dx: f64, dx: f64,
dy: f64, dy: f64,
scroll_y_offset: f64, scroll_offset: (f64, f64),
ctx: &EventCtx, ctx: &EventCtx,
) { ) {
let result = stretch.layout(nodes.pop().unwrap()).unwrap(); let result = stretch.layout(nodes.pop().unwrap()).unwrap();
@ -373,13 +373,15 @@ impl ManagedWidget {
let height: f64 = result.size.height.into(); let height: f64 = result.size.height.into();
let top_left = match self.widget { let top_left = match self.widget {
WidgetType::Slider(ref name) => { WidgetType::Slider(ref name) => {
if name == "scrollbar" { if name == "horiz scrollbar" {
ScreenPt::new(x + dx, y + dy) ScreenPt::new(x + dx, y + dy - scroll_offset.1)
} else if name == "vert scrollbar" {
ScreenPt::new(x + dx - scroll_offset.0, y + dy)
} else { } else {
ScreenPt::new(x + dx, y + dy - scroll_y_offset) ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1)
} }
} }
_ => ScreenPt::new(x + dx, y + dy - scroll_y_offset), _ => ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1),
}; };
self.rect = ScreenRectangle::top_left(top_left, ScreenDims::new(width, height)); self.rect = ScreenRectangle::top_left(top_left, ScreenDims::new(width, height));
if let Some(color) = self.style.bg_color { if let Some(color) = self.style.bg_color {
@ -433,7 +435,7 @@ impl ManagedWidget {
nodes, nodes,
x + dx, x + dx,
y + dy, y + dy,
scroll_y_offset, scroll_offset,
ctx, ctx,
); );
} }
@ -448,7 +450,7 @@ impl ManagedWidget {
nodes, nodes,
x + dx, x + dx,
y + dy, y + dy,
scroll_y_offset, scroll_offset,
ctx, ctx,
); );
} }
@ -499,11 +501,10 @@ pub struct Composite {
menus: HashMap<String, Menu>, menus: HashMap<String, Menu>,
fillers: HashMap<String, Filler>, fillers: HashMap<String, Filler>,
// TODO This needs to clip. scrollable_x: bool,
// TODO Horizontal scrolling? scrollable_y: bool,
scrollable: bool, contents_dims: ScreenDims,
contents_height: f64, container_dims: ScreenDims,
container_height: f64,
clip_rect: Option<ScreenRectangle>, clip_rect: Option<ScreenRectangle>,
} }
@ -580,7 +581,7 @@ impl Composite {
self.layout.horiz, self.layout.horiz,
self.layout.vert, self.layout.vert,
); );
let offset = self.scroll_y_offset(ctx); let offset = self.scroll_offset(ctx);
self.top_level.apply_flexbox( self.top_level.apply_flexbox(
&mut self.sliders, &mut self.sliders,
&mut self.menus, &mut self.menus,
@ -595,41 +596,75 @@ impl Composite {
assert!(nodes.is_empty()); assert!(nodes.is_empty());
} }
fn scroll_y_offset(&self, ctx: &EventCtx) -> f64 { fn scroll_offset(&self, ctx: &EventCtx) -> (f64, f64) {
if self.scrollable { let x = if self.scrollable_x {
self.slider("scrollbar").get_percent() self.slider("horiz scrollbar").get_percent()
* (self.contents_height - self.container_height).max(0.0) * (self.contents_dims.width - self.container_dims.width).max(0.0)
} else { } else {
0.0 0.0
} };
let y = if self.scrollable_y {
self.slider("vert scrollbar").get_percent()
* (self.contents_dims.height - self.container_dims.height).max(0.0)
} else {
0.0
};
(x, y)
} }
fn set_scroll_y_offset(&mut self, ctx: &EventCtx, offset: f64) { fn set_scroll_offset(&mut self, ctx: &EventCtx, offset: (f64, f64)) {
if !self.scrollable { let mut changed = false;
return; if self.scrollable_x {
} changed = true;
let max = (self.contents_height - self.container_height).max(0.0); let max = (self.contents_dims.width - self.container_dims.width).max(0.0);
if max == 0.0 { if max == 0.0 {
assert_eq!(offset, 0.0); assert_eq!(offset.0, 0.0);
self.mut_slider("scrollbar").set_percent(ctx, 0.0); self.mut_slider("horiz scrollbar").set_percent(ctx, 0.0);
} else { } else {
self.mut_slider("scrollbar").set_percent(ctx, offset / max); self.mut_slider("horiz scrollbar")
.set_percent(ctx, offset.0 / max);
} }
}
if self.scrollable_y {
changed = true;
let max = (self.contents_dims.height - self.container_dims.height).max(0.0);
if max == 0.0 {
assert_eq!(offset.1, 0.0);
self.mut_slider("vert scrollbar").set_percent(ctx, 0.0);
} else {
self.mut_slider("vert scrollbar")
.set_percent(ctx, offset.1 / max);
}
}
if changed {
self.recompute_layout(ctx); self.recompute_layout(ctx);
} }
}
pub fn event(&mut self, ctx: &mut EventCtx) -> Option<Outcome> { pub fn event(&mut self, ctx: &mut EventCtx) -> Option<Outcome> {
if self.scrollable if (self.scrollable_x || self.scrollable_y)
&& self && self
.top_level .top_level
.rect .rect
.contains(ctx.canvas.get_cursor_in_screen_space()) .contains(ctx.canvas.get_cursor_in_screen_space())
{ {
if let Some(scroll) = ctx.input.get_mouse_scroll() { if let Some((dx, dy)) = ctx.input.get_mouse_scroll() {
let offset = self.scroll_y_offset(ctx) - scroll * SCROLL_SPEED; let x_offset = if self.scrollable_x {
let max = (self.contents_height - self.container_height).max(0.0); let offset = self.scroll_offset(ctx).0 + dx * SCROLL_SPEED;
// TODO Do the clamping in there instead let max = (self.contents_dims.width - self.container_dims.width).max(0.0);
self.set_scroll_y_offset(ctx, abstutil::clamp(offset, 0.0, max)); abstutil::clamp(offset, 0.0, max)
} else {
0.0
};
let y_offset = if self.scrollable_y {
let offset = self.scroll_offset(ctx).1 - dy * SCROLL_SPEED;
let max = (self.contents_dims.height - self.container_dims.height).max(0.0);
abstutil::clamp(offset, 0.0, max)
} else {
0.0
};
// TODO Refactor the clamping, do it in set_scroll_offset
self.set_scroll_offset(ctx, (x_offset, y_offset));
} }
} }
@ -637,11 +672,11 @@ impl Composite {
self.recompute_layout(ctx); self.recompute_layout(ctx);
} }
let before = self.scroll_y_offset(ctx); let before = self.scroll_offset(ctx);
let result = self let result = self
.top_level .top_level
.event(ctx, &mut self.sliders, &mut self.menus); .event(ctx, &mut self.sliders, &mut self.menus);
if self.scroll_y_offset(ctx) != before { if self.scroll_offset(ctx) != before {
self.recompute_layout(ctx); self.recompute_layout(ctx);
} }
result result
@ -649,11 +684,11 @@ impl Composite {
pub fn draw(&self, g: &mut GfxCtx) { pub fn draw(&self, g: &mut GfxCtx) {
g.canvas.mark_covered_area(self.top_level.rect.clone()); g.canvas.mark_covered_area(self.top_level.rect.clone());
if self.scrollable { if self.scrollable_x || self.scrollable_y {
g.enable_clipping(self.clip_rect.clone().unwrap()); g.enable_clipping(self.clip_rect.clone().unwrap());
} }
self.top_level.draw(g, &self.sliders, &self.menus); self.top_level.draw(g, &self.sliders, &self.menus);
if self.scrollable { if self.scrollable_x || self.scrollable_y {
g.disable_clipping(); g.disable_clipping();
} }
} }
@ -664,16 +699,12 @@ impl Composite {
actions actions
} }
pub fn preserve_scroll(&self, ctx: &EventCtx) -> f64 { pub fn preserve_scroll(&self, ctx: &EventCtx) -> (f64, f64) {
if self.scrollable { self.scroll_offset(ctx)
self.scroll_y_offset(ctx)
} else {
0.0
}
} }
pub fn restore_scroll(&mut self, ctx: &EventCtx, offset: f64) { pub fn restore_scroll(&mut self, ctx: &EventCtx, offset: (f64, f64)) {
self.set_scroll_y_offset(ctx, offset); self.set_scroll_offset(ctx, offset);
} }
pub fn slider(&self, name: &str) -> &Slider { pub fn slider(&self, name: &str) -> &Slider {
@ -702,9 +733,10 @@ impl CompositeBuilder {
menus: self.menus, menus: self.menus,
fillers: self.fillers, fillers: self.fillers,
scrollable: false, scrollable_x: false,
contents_height: 0.0, scrollable_y: false,
container_height: 0.0, contents_dims: ScreenDims::new(0.0, 0.0),
container_dims: ScreenDims::new(0.0, 0.0),
clip_rect: None, clip_rect: None,
}; };
c.recompute_layout(ctx); c.recompute_layout(ctx);
@ -719,41 +751,61 @@ impl CompositeBuilder {
menus: self.menus, menus: self.menus,
fillers: self.fillers, fillers: self.fillers,
scrollable: false, scrollable_x: false,
contents_height: 0.0, scrollable_y: false,
container_height: 0.0, contents_dims: ScreenDims::new(0.0, 0.0),
container_dims: ScreenDims::new(0.0, 0.0),
clip_rect: None, clip_rect: None,
}; };
// If the panel fits without a scrollbar, don't add one. // If the panel fits without a scrollbar, don't add one.
c.recompute_layout(ctx); c.recompute_layout(ctx);
c.contents_height = c.top_level.rect.height(); c.contents_dims = ScreenDims::new(c.top_level.rect.width(), c.top_level.rect.height());
c.container_height = if let Some(pct) = c.layout.percent_height { c.container_dims = ScreenDims::new(
if let Some(pct) = c.layout.percent_width {
ctx.canvas.window_width * pct
} else if c.contents_dims.width < ctx.canvas.window_width {
c.contents_dims.width
} else {
ctx.canvas.window_width
},
if let Some(pct) = c.layout.percent_height {
ctx.canvas.window_height * pct ctx.canvas.window_height * pct
} else if c.contents_height < ctx.canvas.window_height { } else if c.contents_dims.height < ctx.canvas.window_height {
c.contents_height c.contents_dims.height
} else { } else {
ctx.canvas.window_height ctx.canvas.window_height
}; },
if c.contents_height > c.container_height {
c.scrollable = true;
c.sliders.insert(
"scrollbar".to_string(),
Slider::vertical(ctx, c.container_height),
); );
c.top_level = ManagedWidget::row(vec![c.top_level, ManagedWidget::slider("scrollbar")]);
if c.contents_dims.width > c.container_dims.width {
c.scrollable_x = true;
c.sliders.insert(
"horiz scrollbar".to_string(),
Slider::horizontal(ctx, c.container_dims.width),
);
c.top_level =
ManagedWidget::col(vec![c.top_level, ManagedWidget::slider("horiz scrollbar")]);
}
if c.contents_dims.height > c.container_dims.height {
c.scrollable_y = true;
c.sliders.insert(
"vert scrollbar".to_string(),
Slider::vertical(ctx, c.container_dims.height),
);
c.top_level =
ManagedWidget::row(vec![c.top_level, ManagedWidget::slider("vert scrollbar")]);
}
if c.scrollable_x || c.scrollable_y {
c.recompute_layout(ctx); c.recompute_layout(ctx);
let container_width = if let Some(pct) = c.layout.percent_width { let top_left = ctx
ctx.canvas.window_width * pct .canvas
} else { .align_window(c.container_dims, c.layout.horiz, c.layout.vert);
c.top_level.rect.width() c.clip_rect = Some(ScreenRectangle::top_left(top_left, c.container_dims));
};
let dims = ScreenDims::new(container_width, c.container_height);
let top_left = ctx.canvas.align_window(dims, c.layout.horiz, c.layout.vert);
c.clip_rect = Some(ScreenRectangle::top_left(top_left, dims));
} }
ctx.fake_mouseover(|ctx| assert!(c.event(ctx).is_none())); ctx.fake_mouseover(|ctx| assert!(c.event(ctx).is_none()));
c c
} }

View File

@ -89,7 +89,7 @@ impl InfoPanel {
HorizontalAlignment::Percent(0.1), HorizontalAlignment::Percent(0.1),
VerticalAlignment::Percent(0.2), VerticalAlignment::Percent(0.2),
) )
.size_percent(30, 70) .size_percent(30, 30)
.build_scrollable(ctx), .build_scrollable(ctx),
} }
} }