mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-29 17:34:58 +03:00
support horizontal scrolling
This commit is contained in:
parent
6db84efdae
commit
d637243f2a
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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 {
|
||||
|
@ -109,9 +109,9 @@ impl UserInput {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get_mouse_scroll(&self) -> Option<f64> {
|
||||
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
|
||||
}
|
||||
|
@ -363,7 +363,7 @@ impl ManagedWidget {
|
||||
nodes: &mut Vec<Node>,
|
||||
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<String, Menu>,
|
||||
fillers: HashMap<String, Filler>,
|
||||
|
||||
// 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<ScreenRectangle>,
|
||||
}
|
||||
|
||||
@ -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<Outcome> {
|
||||
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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user