mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-29 17:34:58 +03:00
yay, delete old gunky attempts at scrolling
This commit is contained in:
parent
b229c44511
commit
49a0f7abd6
@ -22,8 +22,8 @@ pub use crate::runner::{run, EventLoopMode, Settings, GUI};
|
||||
pub use crate::screen_geom::{ScreenDims, ScreenPt, ScreenRectangle};
|
||||
pub use crate::text::{Line, Text, TextSpan, HOTKEY_COLOR};
|
||||
pub use crate::widgets::{
|
||||
Autocomplete, Button, Choice, ItemSlider, JustDraw, ModalMenu, NewScroller, Scroller, Slider,
|
||||
SliderWithTextBox, Warper, WarpingItemSlider, Wizard, WrappedWizard,
|
||||
Autocomplete, Button, Choice, ItemSlider, JustDraw, ModalMenu, Slider, SliderWithTextBox,
|
||||
Warper, WarpingItemSlider, Wizard, WrappedWizard,
|
||||
};
|
||||
|
||||
pub enum InputResult<T: Clone> {
|
||||
|
@ -5,7 +5,6 @@ mod modal_menu;
|
||||
mod no_op;
|
||||
mod popup_menu;
|
||||
mod screenshot;
|
||||
mod scroller;
|
||||
mod slider;
|
||||
mod text_box;
|
||||
mod warper;
|
||||
@ -17,7 +16,6 @@ pub use self::modal_menu::ModalMenu;
|
||||
pub use self::no_op::JustDraw;
|
||||
pub(crate) use self::popup_menu::PopupMenu;
|
||||
pub(crate) use self::screenshot::{screenshot_current, screenshot_everything};
|
||||
pub use self::scroller::{NewScroller, Scroller};
|
||||
pub use self::slider::{ItemSlider, Slider, SliderWithTextBox, WarpingItemSlider};
|
||||
pub use self::warper::Warper;
|
||||
pub use self::wizard::{Choice, Wizard, WrappedWizard};
|
||||
|
@ -1,359 +0,0 @@
|
||||
use crate::{
|
||||
text, Canvas, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Line, ScreenDims, ScreenPt,
|
||||
ScreenRectangle, Text,
|
||||
};
|
||||
use geom::{Distance, Polygon, Pt2D};
|
||||
use ordered_float::NotNan;
|
||||
|
||||
enum Item<T: Clone + Copy> {
|
||||
UpButton,
|
||||
DownButton,
|
||||
Actual(T),
|
||||
}
|
||||
|
||||
// TODO Unify with Menu?
|
||||
// TODO Handle window resizing generally
|
||||
// TODO Hide scrolling buttons if not needed... or maybe that's an inconsistent UX
|
||||
pub struct Scroller<T: Clone + Copy> {
|
||||
// TODO Maybe the height of each thing; insist that the width is the same for all?
|
||||
items: Vec<(Item<T>, ScreenDims)>,
|
||||
|
||||
master_topleft: ScreenPt,
|
||||
hovering_on: Option<usize>,
|
||||
bg_color: Color,
|
||||
hovering_color: Color,
|
||||
current_selection_color: Color,
|
||||
|
||||
// Does NOT include buttons!
|
||||
top_idx: usize,
|
||||
current_selection: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone + Copy> Scroller<T> {
|
||||
pub fn new(
|
||||
master_topleft: ScreenPt,
|
||||
actual_items: Vec<(T, ScreenDims)>,
|
||||
current_selection: usize,
|
||||
ctx: &EventCtx,
|
||||
) -> Scroller<T> {
|
||||
let max_width = actual_items
|
||||
.iter()
|
||||
.map(|(_, dims)| dims.width)
|
||||
.max_by_key(|w| NotNan::new(*w).unwrap())
|
||||
.unwrap();
|
||||
let mut items = vec![(
|
||||
Item::UpButton,
|
||||
ScreenDims::new(max_width, ctx.default_line_height()),
|
||||
)];
|
||||
for (item, dims) in actual_items {
|
||||
items.push((Item::Actual(item), dims));
|
||||
}
|
||||
items.push((
|
||||
Item::DownButton,
|
||||
ScreenDims::new(max_width, ctx.default_line_height()),
|
||||
));
|
||||
|
||||
let top_idx = current_selection;
|
||||
// TODO Try to start with current_selection centered, ideally. Or at least start a bit up
|
||||
// in this case. :\
|
||||
|
||||
Scroller {
|
||||
items,
|
||||
master_topleft,
|
||||
hovering_on: None,
|
||||
// TODO ctx.cs
|
||||
bg_color: Color::BLACK.alpha(0.95),
|
||||
hovering_color: Color::RED.alpha(0.95),
|
||||
current_selection_color: Color::BLUE.alpha(0.95),
|
||||
top_idx,
|
||||
current_selection,
|
||||
}
|
||||
}
|
||||
|
||||
// Includes buttons!
|
||||
fn get_visible_items(&self, canvas: &Canvas) -> Vec<(usize, ScreenRectangle)> {
|
||||
// Up button
|
||||
let mut visible = vec![(
|
||||
0,
|
||||
ScreenRectangle {
|
||||
x1: self.master_topleft.x,
|
||||
y1: self.master_topleft.y,
|
||||
x2: self.master_topleft.x + self.items[0].1.width,
|
||||
y2: self.master_topleft.y + self.items[0].1.height,
|
||||
},
|
||||
)];
|
||||
|
||||
// Include the two buttons here
|
||||
let mut space_left = canvas.window_height - (2.0 * self.items[0].1.height);
|
||||
let mut y1 = visible[0].1.y2;
|
||||
|
||||
for idx in 1 + self.top_idx..self.items.len() - 1 {
|
||||
if self.items[idx].1.height > space_left {
|
||||
break;
|
||||
}
|
||||
visible.push((
|
||||
idx,
|
||||
ScreenRectangle {
|
||||
x1: self.master_topleft.x,
|
||||
y1,
|
||||
x2: self.master_topleft.x + self.items[idx].1.width,
|
||||
y2: y1 + self.items[idx].1.height,
|
||||
},
|
||||
));
|
||||
y1 += self.items[idx].1.height;
|
||||
space_left -= self.items[idx].1.height;
|
||||
}
|
||||
|
||||
// Down button
|
||||
visible.push((
|
||||
self.items.len() - 1,
|
||||
ScreenRectangle {
|
||||
x1: self.master_topleft.x,
|
||||
y1,
|
||||
x2: self.master_topleft.x + self.items[0].1.width,
|
||||
y2: y1 + self.items[0].1.height,
|
||||
},
|
||||
));
|
||||
|
||||
visible
|
||||
}
|
||||
|
||||
fn num_items_hidden_below(&self, canvas: &Canvas) -> usize {
|
||||
let visible = self.get_visible_items(canvas);
|
||||
// Ignore the down button
|
||||
let last_idx = visible[visible.len() - 2].0;
|
||||
self.items.len() - 2 - last_idx
|
||||
}
|
||||
|
||||
// Returns the item selected, if it changes
|
||||
pub fn event(&mut self, ctx: &mut EventCtx) -> Option<T> {
|
||||
if ctx.redo_mouseover() {
|
||||
let cursor = ctx.canvas.get_cursor_in_screen_space();
|
||||
self.hovering_on = None;
|
||||
for (idx, rect) in self.get_visible_items(ctx.canvas) {
|
||||
if rect.contains(cursor) {
|
||||
self.hovering_on = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(idx) = self.hovering_on {
|
||||
if ctx.normal_left_click() {
|
||||
match self.items[idx].0 {
|
||||
Item::UpButton => {
|
||||
if self.top_idx != 0 {
|
||||
self.top_idx -= 1;
|
||||
}
|
||||
}
|
||||
Item::DownButton => {
|
||||
if self.num_items_hidden_below(ctx.canvas) != 0 {
|
||||
self.top_idx += 1;
|
||||
}
|
||||
}
|
||||
Item::Actual(item) => {
|
||||
self.current_selection = idx - 1;
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// Returns the items to draw and the space they occupy.
|
||||
pub fn draw(&self, g: &mut GfxCtx) -> Vec<(T, ScreenRectangle)> {
|
||||
let visible = self.get_visible_items(g.canvas);
|
||||
// We know buttons have the max_width.
|
||||
let max_width = visible[0].1.width();
|
||||
let mut total_height = 0.0;
|
||||
for (_, rect) in &visible {
|
||||
total_height += rect.height();
|
||||
}
|
||||
|
||||
g.fork_screenspace();
|
||||
g.draw_polygon(
|
||||
self.bg_color,
|
||||
&Polygon::rectangle_topleft(
|
||||
self.master_topleft.to_pt(),
|
||||
Distance::meters(max_width),
|
||||
Distance::meters(total_height),
|
||||
),
|
||||
);
|
||||
g.canvas.mark_covered_area(ScreenRectangle::top_left(
|
||||
self.master_topleft,
|
||||
ScreenDims::new(max_width, total_height),
|
||||
));
|
||||
|
||||
let mut items = Vec::new();
|
||||
for (idx, rect) in visible {
|
||||
if Some(idx) == self.hovering_on || idx == self.current_selection + 1 {
|
||||
// Drawing text keeps reseting this. :(
|
||||
g.fork_screenspace();
|
||||
g.draw_polygon(
|
||||
if Some(idx) == self.hovering_on {
|
||||
self.hovering_color
|
||||
} else {
|
||||
self.current_selection_color
|
||||
},
|
||||
&Polygon::rectangle_topleft(
|
||||
Pt2D::new(rect.x1, rect.y1),
|
||||
Distance::meters(rect.width()),
|
||||
Distance::meters(rect.height()),
|
||||
),
|
||||
);
|
||||
}
|
||||
match self.items[idx].0 {
|
||||
Item::UpButton => {
|
||||
// TODO center the text inside the rectangle. and actually, g should have a
|
||||
// method for that.
|
||||
let mut txt = Text::new();
|
||||
if self.top_idx == 0 {
|
||||
// TODO text::INACTIVE_CHOICE_COLOR
|
||||
txt.add(Line("scroll up").fg(Color::grey(0.4)));
|
||||
} else {
|
||||
txt.add(Line(format!("scroll up ({} more items)", self.top_idx)));
|
||||
}
|
||||
g.draw_text_at_screenspace_topleft(&txt, ScreenPt::new(rect.x1, rect.y1));
|
||||
}
|
||||
Item::DownButton => {
|
||||
let mut txt = Text::new();
|
||||
let num_items = self.num_items_hidden_below(g.canvas);
|
||||
if num_items == 0 {
|
||||
txt.add(Line("scroll down").fg(Color::grey(0.4)));
|
||||
} else {
|
||||
txt.add(Line(format!("scroll down ({} more items)", num_items)));
|
||||
}
|
||||
g.draw_text_at_screenspace_topleft(&txt, ScreenPt::new(rect.x1, rect.y1));
|
||||
}
|
||||
Item::Actual(item) => {
|
||||
items.push((item, rect));
|
||||
}
|
||||
}
|
||||
}
|
||||
g.unfork();
|
||||
|
||||
items
|
||||
}
|
||||
|
||||
pub fn select_previous(&mut self) {
|
||||
assert!(self.current_selection != 0);
|
||||
self.current_selection -= 1;
|
||||
// TODO This and the case below aren't right; we might scroll far past the current
|
||||
// selection. Need similar logic for initializing Scroller and make sure the new
|
||||
// current_selection is "centered", but also retain consistency.
|
||||
if self.current_selection < self.top_idx {
|
||||
self.top_idx -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_next(&mut self, canvas: &Canvas) {
|
||||
assert!(self.current_selection != self.items.len() - 2);
|
||||
self.current_selection += 1;
|
||||
// Remember, the indices include buttons. :(
|
||||
if self
|
||||
.get_visible_items(canvas)
|
||||
.into_iter()
|
||||
.find(|(idx, _)| self.current_selection + 1 == *idx)
|
||||
.is_none()
|
||||
{
|
||||
self.top_idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_idx(&self) -> usize {
|
||||
self.current_selection
|
||||
}
|
||||
|
||||
pub fn num_items(&self) -> usize {
|
||||
self.items.len() - 2
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NewScroller {
|
||||
draw: Drawable,
|
||||
txts: Vec<(Text, ScreenPt)>,
|
||||
_total_dims: ScreenDims,
|
||||
zoom: f64,
|
||||
|
||||
offset: f64,
|
||||
|
||||
top_left: ScreenPt,
|
||||
dims: ScreenDims,
|
||||
}
|
||||
|
||||
impl NewScroller {
|
||||
// geom and multi_txt should be in screen-space, with the top_left as (0.0, 0.0).
|
||||
pub fn new(
|
||||
geom: GeomBatch,
|
||||
txts: Vec<(Text, ScreenPt)>,
|
||||
zoom: f64,
|
||||
ctx: &EventCtx,
|
||||
) -> NewScroller {
|
||||
let mut total_dims = geom.get_dims();
|
||||
for (txt, top_left) in &txts {
|
||||
let mut dims = ctx.text_dims(txt);
|
||||
dims.width += top_left.x;
|
||||
dims.height += top_left.y;
|
||||
if dims.width > total_dims.width {
|
||||
total_dims.width = dims.width;
|
||||
}
|
||||
if dims.height > total_dims.height {
|
||||
total_dims.height = dims.height;
|
||||
}
|
||||
}
|
||||
|
||||
NewScroller {
|
||||
draw: geom.upload(ctx),
|
||||
txts,
|
||||
_total_dims: total_dims,
|
||||
zoom,
|
||||
|
||||
offset: 0.0,
|
||||
|
||||
// TODO The layouting is hardcoded
|
||||
top_left: ScreenPt::new(0.0, 0.0),
|
||||
dims: ScreenDims::new(100.0, 100.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(&mut self, ctx: &mut EventCtx) {
|
||||
let rect = ScreenRectangle::top_left(self.top_left, self.dims);
|
||||
if rect.contains(ctx.canvas.get_cursor_in_screen_space()) {
|
||||
if let Some(scroll) = ctx.input.get_mouse_scroll() {
|
||||
self.offset -= scroll;
|
||||
// TODO Clamp... or maybe last minute, based on dims, which'll get updated by
|
||||
// window resizing and such
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
let rect = ScreenRectangle::top_left(self.top_left, self.dims);
|
||||
g.canvas.mark_covered_area(rect);
|
||||
|
||||
g.fork_screenspace();
|
||||
g.draw_polygon(
|
||||
text::BG_COLOR,
|
||||
&Polygon::rectangle_topleft(
|
||||
Pt2D::new(0.0, 0.0),
|
||||
Distance::meters(self.dims.width),
|
||||
Distance::meters(self.dims.height),
|
||||
),
|
||||
);
|
||||
g.unfork();
|
||||
|
||||
g.fork(Pt2D::new(0.0, self.offset), self.top_left, self.zoom);
|
||||
g.redraw(&self.draw);
|
||||
g.unfork();
|
||||
|
||||
for (txt, pt) in &self.txts {
|
||||
g.draw_text_at_screenspace_topleft(
|
||||
txt,
|
||||
ScreenPt::new(pt.x, pt.y - self.offset * self.zoom),
|
||||
);
|
||||
}
|
||||
|
||||
// TODO draw scrollbar
|
||||
}
|
||||
}
|
@ -142,7 +142,7 @@ impl State for ShowTrafficSignal {
|
||||
&ui.primary.sim,
|
||||
&ShowEverything::new(),
|
||||
);
|
||||
self.diagram.draw(g, &ui.draw_ctx());
|
||||
self.diagram.draw(g);
|
||||
|
||||
self.menu.draw(g);
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ impl State for TrafficSignalEditor {
|
||||
}
|
||||
batch.draw(g);
|
||||
|
||||
self.diagram.draw(g, &ctx);
|
||||
self.diagram.draw(g);
|
||||
|
||||
self.menu.draw(g);
|
||||
if let Some(id) = self.group_selected {
|
||||
|
@ -587,13 +587,13 @@ const SCROLL_SPEED: f64 = 5.0;
|
||||
// TODO This doesn't clip. There's no way to express that the scrollable thing should occupy a
|
||||
// small part of the screen.
|
||||
// TODO Horizontal scrolling?
|
||||
pub struct CompositeScroller {
|
||||
pub struct Scroller {
|
||||
composite: Composite,
|
||||
}
|
||||
|
||||
impl CompositeScroller {
|
||||
pub fn new(composite: Composite) -> CompositeScroller {
|
||||
CompositeScroller { composite }
|
||||
impl Scroller {
|
||||
pub fn new(composite: Composite) -> Scroller {
|
||||
Scroller { composite }
|
||||
}
|
||||
|
||||
pub fn event(&mut self, ctx: &mut EventCtx, ui: &mut UI) -> Option<Outcome> {
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::managed::{Composite, CompositeScroller, ManagedWidget, Outcome};
|
||||
use crate::managed::{Composite, ManagedWidget, Outcome, Scroller};
|
||||
use crate::options::TrafficSignalStyle;
|
||||
use crate::render::{DrawCtx, DrawTurnGroup, BIG_ARROW_THICKNESS};
|
||||
use crate::ui::UI;
|
||||
use ezgui::{
|
||||
Button, Color, DrawBoth, EventCtx, GeomBatch, GfxCtx, HorizontalAlignment, Line, ModalMenu,
|
||||
NewScroller, ScreenDims, ScreenPt, Scroller, Text, VerticalAlignment,
|
||||
Text, VerticalAlignment,
|
||||
};
|
||||
use geom::{Circle, Distance, Duration, Polygon, Pt2D};
|
||||
use map_model::{IntersectionID, Phase, TurnPriority};
|
||||
@ -133,13 +133,9 @@ pub fn draw_signal_phase(
|
||||
);
|
||||
}
|
||||
|
||||
const PADDING: f64 = 5.0;
|
||||
// Not counting labels
|
||||
const PERCENT_WIDTH: f64 = 0.15;
|
||||
|
||||
pub struct TrafficSignalDiagram {
|
||||
pub i: IntersectionID,
|
||||
scroller: CompositeScroller,
|
||||
scroller: Scroller,
|
||||
current_phase: usize,
|
||||
}
|
||||
|
||||
@ -186,48 +182,17 @@ impl TrafficSignalDiagram {
|
||||
self.current_phase
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx, ctx: &DrawCtx) {
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
self.scroller.draw(g);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_new_scroller(i: IntersectionID, draw_ctx: &DrawCtx, ctx: &EventCtx) -> NewScroller {
|
||||
let zoom = 15.0;
|
||||
|
||||
// TODO Nicer API would be passing in a list of (GeomBatch, text-at-points)s each starting at the
|
||||
// origin, then do the translation later.
|
||||
let mut master_batch = GeomBatch::new();
|
||||
let mut txt: Vec<(Text, ScreenPt)> = Vec::new();
|
||||
|
||||
// Slightly inaccurate -- the turn rendering may slightly exceed the intersection polygon --
|
||||
// but this is close enough.
|
||||
let bounds = draw_ctx.map.get_i(i).polygon.get_bounds();
|
||||
let mut y_offset = 0.0;
|
||||
for (idx, phase) in draw_ctx.map.get_traffic_signal(i).phases.iter().enumerate() {
|
||||
let mut batch = GeomBatch::new();
|
||||
draw_signal_phase(phase, i, None, &mut batch, draw_ctx);
|
||||
for (color, poly) in batch.consume() {
|
||||
master_batch.push(
|
||||
color,
|
||||
poly.translate(-bounds.min_x, y_offset - bounds.min_y),
|
||||
);
|
||||
}
|
||||
txt.push((
|
||||
Text::from(Line(format!("Phase {}: {}", idx + 1, phase.duration))).with_bg(),
|
||||
ScreenPt::new(10.0 + (bounds.max_x - bounds.min_x) * zoom, y_offset * zoom),
|
||||
));
|
||||
y_offset += bounds.max_y - bounds.min_y;
|
||||
}
|
||||
|
||||
NewScroller::new(master_batch, txt, zoom, ctx)
|
||||
}
|
||||
|
||||
fn make_scroller(
|
||||
i: IntersectionID,
|
||||
selected: usize,
|
||||
draw_ctx: &DrawCtx,
|
||||
ctx: &EventCtx,
|
||||
) -> CompositeScroller {
|
||||
) -> Scroller {
|
||||
let zoom = 20.0;
|
||||
// Slightly inaccurate -- the turn rendering may slightly exceed the intersection polygon --
|
||||
// but this is close enough.
|
||||
@ -287,7 +252,7 @@ fn make_scroller(
|
||||
);
|
||||
}
|
||||
|
||||
CompositeScroller::new(Composite::aligned(
|
||||
Scroller::new(Composite::aligned(
|
||||
(HorizontalAlignment::Left, VerticalAlignment::Top),
|
||||
ManagedWidget::col(col).bg(Color::grey(0.4)),
|
||||
))
|
||||
|
Loading…
Reference in New Issue
Block a user