From d637243f2a58119ae2a654fddc1941e1a10aacc3 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Tue, 14 Jan 2020 15:47:35 -0800 Subject: [PATCH] support horizontal scrolling --- ezgui/src/canvas.rs | 2 +- ezgui/src/event.rs | 11 +-- ezgui/src/event_ctx.rs | 6 +- ezgui/src/input.rs | 6 +- ezgui/src/managed.rs | 198 +++++++++++++++++++++++++--------------- game/src/common/info.rs | 2 +- 6 files changed, 140 insertions(+), 85 deletions(-) diff --git a/ezgui/src/canvas.rs b/ezgui/src/canvas.rs index c437fb3de3..58dcce4a76 100644 --- a/ezgui/src/canvas.rs +++ b/ezgui/src/canvas.rs @@ -86,7 +86,7 @@ impl Canvas { 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; // By popular request, some limits ;) self.cam_zoom = 1.1_f64 diff --git a/ezgui/src/event.rs b/ezgui/src/event.rs index e2d5e0cb28..429eba2aea 100644 --- a/ezgui/src/event.rs +++ b/ezgui/src/event.rs @@ -19,8 +19,7 @@ pub enum Event { MouseMovedTo(ScreenPt), WindowLostCursor, WindowGainedCursor, - // Vertical only - MouseWheelScroll(f64), + MouseWheelScroll(f64, f64), WindowResized(f64, f64), } @@ -58,18 +57,18 @@ impl Event { Some(Event::MouseMovedTo(ScreenPt::new(pos.x, pos.y))) } glutin::WindowEvent::MouseWheel { delta, .. } => match delta { - glutin::MouseScrollDelta::LineDelta(_, dy) => { - if dy == 0.0 { + glutin::MouseScrollDelta::LineDelta(dx, dy) => { + if dx == 0.0 && dy == 0.0 { None } 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. // Probably the better way is to convert the LogicalPosition to a PhysicalPosition // somehow knowing the DPI. 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) => { diff --git a/ezgui/src/event_ctx.rs b/ezgui/src/event_ctx.rs index 7aef02cb25..2e472cf022 100644 --- a/ezgui/src/event_ctx.rs +++ b/ezgui/src/event_ctx.rs @@ -62,7 +62,11 @@ impl<'a> EventCtx<'a> { self.fake_mouseover || self.input.window_lost_cursor() || (!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 { diff --git a/ezgui/src/input.rs b/ezgui/src/input.rs index a716e50024..ecda6352dd 100644 --- a/ezgui/src/input.rs +++ b/ezgui/src/input.rs @@ -109,9 +109,9 @@ impl UserInput { None } - pub fn get_mouse_scroll(&self) -> Option { - if let Event::MouseWheelScroll(dy) = self.event { - return Some(dy); + pub(crate) fn get_mouse_scroll(&self) -> Option<(f64, f64)> { + if let Event::MouseWheelScroll(dx, dy) = self.event { + return Some((dx, dy)); } None } diff --git a/ezgui/src/managed.rs b/ezgui/src/managed.rs index 745a893909..134723a0fd 100644 --- a/ezgui/src/managed.rs +++ b/ezgui/src/managed.rs @@ -363,7 +363,7 @@ impl ManagedWidget { nodes: &mut Vec, dx: f64, dy: f64, - scroll_y_offset: f64, + scroll_offset: (f64, f64), ctx: &EventCtx, ) { let result = stretch.layout(nodes.pop().unwrap()).unwrap(); @@ -373,13 +373,15 @@ impl ManagedWidget { let height: f64 = result.size.height.into(); let top_left = match self.widget { WidgetType::Slider(ref name) => { - if name == "scrollbar" { - ScreenPt::new(x + dx, y + dy) + if name == "horiz scrollbar" { + ScreenPt::new(x + dx, y + dy - scroll_offset.1) + } else if name == "vert scrollbar" { + ScreenPt::new(x + dx - scroll_offset.0, y + dy) } 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)); if let Some(color) = self.style.bg_color { @@ -433,7 +435,7 @@ impl ManagedWidget { nodes, x + dx, y + dy, - scroll_y_offset, + scroll_offset, ctx, ); } @@ -448,7 +450,7 @@ impl ManagedWidget { nodes, x + dx, y + dy, - scroll_y_offset, + scroll_offset, ctx, ); } @@ -499,11 +501,10 @@ pub struct Composite { menus: HashMap, fillers: HashMap, - // TODO This needs to clip. - // TODO Horizontal scrolling? - scrollable: bool, - contents_height: f64, - container_height: f64, + scrollable_x: bool, + scrollable_y: bool, + contents_dims: ScreenDims, + container_dims: ScreenDims, clip_rect: Option, } @@ -580,7 +581,7 @@ impl Composite { self.layout.horiz, self.layout.vert, ); - let offset = self.scroll_y_offset(ctx); + let offset = self.scroll_offset(ctx); self.top_level.apply_flexbox( &mut self.sliders, &mut self.menus, @@ -595,41 +596,75 @@ impl Composite { assert!(nodes.is_empty()); } - fn scroll_y_offset(&self, ctx: &EventCtx) -> f64 { - if self.scrollable { - self.slider("scrollbar").get_percent() - * (self.contents_height - self.container_height).max(0.0) + fn scroll_offset(&self, ctx: &EventCtx) -> (f64, f64) { + let x = if self.scrollable_x { + self.slider("horiz scrollbar").get_percent() + * (self.contents_dims.width - self.container_dims.width).max(0.0) } else { 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) { - if !self.scrollable { - return; + fn set_scroll_offset(&mut self, ctx: &EventCtx, offset: (f64, f64)) { + let mut changed = false; + if self.scrollable_x { + changed = true; + let max = (self.contents_dims.width - self.container_dims.width).max(0.0); + if max == 0.0 { + assert_eq!(offset.0, 0.0); + self.mut_slider("horiz scrollbar").set_percent(ctx, 0.0); + } else { + self.mut_slider("horiz scrollbar") + .set_percent(ctx, offset.0 / max); + } } - let max = (self.contents_height - self.container_height).max(0.0); - if max == 0.0 { - assert_eq!(offset, 0.0); - self.mut_slider("scrollbar").set_percent(ctx, 0.0); - } else { - self.mut_slider("scrollbar").set_percent(ctx, offset / 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 { - if self.scrollable + if (self.scrollable_x || self.scrollable_y) && self .top_level .rect .contains(ctx.canvas.get_cursor_in_screen_space()) { - if let Some(scroll) = ctx.input.get_mouse_scroll() { - let offset = self.scroll_y_offset(ctx) - scroll * SCROLL_SPEED; - let max = (self.contents_height - self.container_height).max(0.0); - // TODO Do the clamping in there instead - self.set_scroll_y_offset(ctx, abstutil::clamp(offset, 0.0, max)); + if let Some((dx, dy)) = ctx.input.get_mouse_scroll() { + let x_offset = if self.scrollable_x { + let offset = self.scroll_offset(ctx).0 + dx * SCROLL_SPEED; + let max = (self.contents_dims.width - self.container_dims.width).max(0.0); + 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); } - let before = self.scroll_y_offset(ctx); + let before = self.scroll_offset(ctx); let result = self .top_level .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); } result @@ -649,11 +684,11 @@ impl Composite { pub fn draw(&self, g: &mut GfxCtx) { 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()); } self.top_level.draw(g, &self.sliders, &self.menus); - if self.scrollable { + if self.scrollable_x || self.scrollable_y { g.disable_clipping(); } } @@ -664,16 +699,12 @@ impl Composite { actions } - pub fn preserve_scroll(&self, ctx: &EventCtx) -> f64 { - if self.scrollable { - self.scroll_y_offset(ctx) - } else { - 0.0 - } + pub fn preserve_scroll(&self, ctx: &EventCtx) -> (f64, f64) { + self.scroll_offset(ctx) } - pub fn restore_scroll(&mut self, ctx: &EventCtx, offset: f64) { - self.set_scroll_y_offset(ctx, offset); + pub fn restore_scroll(&mut self, ctx: &EventCtx, offset: (f64, f64)) { + self.set_scroll_offset(ctx, offset); } pub fn slider(&self, name: &str) -> &Slider { @@ -702,9 +733,10 @@ impl CompositeBuilder { menus: self.menus, fillers: self.fillers, - scrollable: false, - contents_height: 0.0, - container_height: 0.0, + scrollable_x: false, + scrollable_y: false, + contents_dims: ScreenDims::new(0.0, 0.0), + container_dims: ScreenDims::new(0.0, 0.0), clip_rect: None, }; c.recompute_layout(ctx); @@ -719,41 +751,61 @@ impl CompositeBuilder { menus: self.menus, fillers: self.fillers, - scrollable: false, - contents_height: 0.0, - container_height: 0.0, + scrollable_x: false, + scrollable_y: false, + contents_dims: ScreenDims::new(0.0, 0.0), + container_dims: ScreenDims::new(0.0, 0.0), clip_rect: None, }; // If the panel fits without a scrollbar, don't add one. c.recompute_layout(ctx); - c.contents_height = c.top_level.rect.height(); - c.container_height = if let Some(pct) = c.layout.percent_height { - ctx.canvas.window_height * pct - } else if c.contents_height < ctx.canvas.window_height { - c.contents_height - } else { - ctx.canvas.window_height - }; + c.contents_dims = ScreenDims::new(c.top_level.rect.width(), c.top_level.rect.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 + } else if c.contents_dims.height < ctx.canvas.window_height { + c.contents_dims.height + } else { + ctx.canvas.window_height + }, + ); - if c.contents_height > c.container_height { - c.scrollable = true; + if c.contents_dims.width > c.container_dims.width { + c.scrollable_x = true; c.sliders.insert( - "scrollbar".to_string(), - Slider::vertical(ctx, c.container_height), + "horiz scrollbar".to_string(), + Slider::horizontal(ctx, c.container_dims.width), ); - c.top_level = ManagedWidget::row(vec![c.top_level, ManagedWidget::slider("scrollbar")]); + 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); - let container_width = if let Some(pct) = c.layout.percent_width { - ctx.canvas.window_width * pct - } else { - c.top_level.rect.width() - }; - 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)); + let top_left = ctx + .canvas + .align_window(c.container_dims, c.layout.horiz, c.layout.vert); + c.clip_rect = Some(ScreenRectangle::top_left(top_left, c.container_dims)); } + ctx.fake_mouseover(|ctx| assert!(c.event(ctx).is_none())); c } diff --git a/game/src/common/info.rs b/game/src/common/info.rs index 54a45caeb7..476163f12a 100644 --- a/game/src/common/info.rs +++ b/game/src/common/info.rs @@ -89,7 +89,7 @@ impl InfoPanel { HorizontalAlignment::Percent(0.1), VerticalAlignment::Percent(0.2), ) - .size_percent(30, 70) + .size_percent(30, 30) .build_scrollable(ctx), } }