2019-12-21 02:56:04 +03:00
|
|
|
use crate::layout::Widget;
|
2020-01-02 20:35:41 +03:00
|
|
|
use crate::widgets::PopupMenu;
|
2019-12-21 02:56:04 +03:00
|
|
|
use crate::{
|
2019-12-29 19:16:38 +03:00
|
|
|
Button, Color, DrawBoth, EventCtx, Filler, GeomBatch, GfxCtx, Histogram, HorizontalAlignment,
|
2020-01-14 02:56:25 +03:00
|
|
|
JustDraw, Plot, RewriteColor, ScreenDims, ScreenPt, ScreenRectangle, Slider, Text,
|
|
|
|
VerticalAlignment,
|
2019-12-21 02:56:04 +03:00
|
|
|
};
|
2020-01-02 20:35:41 +03:00
|
|
|
use abstutil::Cloneable;
|
2019-12-21 02:56:04 +03:00
|
|
|
use geom::{Distance, Duration, Polygon};
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
use stretch::geometry::{Rect, Size};
|
|
|
|
use stretch::node::{Node, Stretch};
|
2020-01-15 02:18:27 +03:00
|
|
|
use stretch::number::Number;
|
2020-01-15 03:46:45 +03:00
|
|
|
use stretch::style::{
|
|
|
|
AlignItems, Dimension, FlexDirection, FlexWrap, JustifyContent, PositionType, Style,
|
|
|
|
};
|
2019-12-21 02:56:04 +03:00
|
|
|
|
2020-01-02 20:35:41 +03:00
|
|
|
type Menu = PopupMenu<Box<dyn Cloneable>>;
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
pub struct ManagedWidget {
|
|
|
|
widget: WidgetType,
|
|
|
|
style: LayoutStyle,
|
|
|
|
rect: ScreenRectangle,
|
2019-12-22 21:52:41 +03:00
|
|
|
bg: Option<DrawBoth>,
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
enum WidgetType {
|
|
|
|
Draw(JustDraw),
|
|
|
|
Btn(Button),
|
|
|
|
Slider(String),
|
2020-01-02 20:35:41 +03:00
|
|
|
Menu(String),
|
2019-12-25 23:01:51 +03:00
|
|
|
Filler(String),
|
2019-12-21 02:56:04 +03:00
|
|
|
// TODO Sadness. Can't have some kind of wildcard generic here?
|
|
|
|
DurationPlot(Plot<Duration>),
|
|
|
|
UsizePlot(Plot<usize>),
|
2019-12-29 19:16:38 +03:00
|
|
|
Histogram(Histogram),
|
2019-12-21 02:56:04 +03:00
|
|
|
Row(Vec<ManagedWidget>),
|
|
|
|
Column(Vec<ManagedWidget>),
|
|
|
|
}
|
|
|
|
|
|
|
|
struct LayoutStyle {
|
|
|
|
bg_color: Option<Color>,
|
2020-01-24 02:35:08 +03:00
|
|
|
outline: Option<(f64, Color)>,
|
2019-12-21 02:56:04 +03:00
|
|
|
align_items: Option<AlignItems>,
|
|
|
|
justify_content: Option<JustifyContent>,
|
|
|
|
flex_wrap: Option<FlexWrap>,
|
2019-12-23 22:05:24 +03:00
|
|
|
size: Option<Size<Dimension>>,
|
2019-12-21 02:56:04 +03:00
|
|
|
padding: Option<Rect<Dimension>>,
|
|
|
|
margin: Option<Rect<Dimension>>,
|
2020-01-15 03:46:45 +03:00
|
|
|
position_type: Option<PositionType>,
|
|
|
|
position: Option<Rect<Dimension>>,
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl LayoutStyle {
|
|
|
|
fn apply(&self, style: &mut Style) {
|
|
|
|
if let Some(x) = self.align_items {
|
|
|
|
style.align_items = x;
|
|
|
|
}
|
|
|
|
if let Some(x) = self.justify_content {
|
|
|
|
style.justify_content = x;
|
|
|
|
}
|
|
|
|
if let Some(x) = self.flex_wrap {
|
|
|
|
style.flex_wrap = x;
|
|
|
|
}
|
2019-12-23 22:05:24 +03:00
|
|
|
if let Some(x) = self.size {
|
|
|
|
style.size = x;
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
if let Some(x) = self.padding {
|
|
|
|
style.padding = x;
|
|
|
|
}
|
|
|
|
if let Some(x) = self.margin {
|
|
|
|
style.margin = x;
|
|
|
|
}
|
2020-01-15 03:46:45 +03:00
|
|
|
if let Some(x) = self.position_type {
|
|
|
|
style.position_type = x;
|
|
|
|
}
|
|
|
|
if let Some(x) = self.position {
|
|
|
|
style.position = x;
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Layouting
|
|
|
|
// TODO Maybe I just want margin, not padding. And maybe more granular controls per side. And to
|
|
|
|
// apply margin to everything in a row or column.
|
|
|
|
// TODO Row and columns feel backwards when using them.
|
|
|
|
impl ManagedWidget {
|
|
|
|
pub fn centered(mut self) -> ManagedWidget {
|
|
|
|
self.style.align_items = Some(AlignItems::Center);
|
|
|
|
self.style.justify_content = Some(JustifyContent::SpaceAround);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-01-29 23:16:18 +03:00
|
|
|
pub fn centered_horiz(self) -> ManagedWidget {
|
|
|
|
ManagedWidget::row(vec![self]).centered()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn centered_vert(self) -> ManagedWidget {
|
|
|
|
ManagedWidget::col(vec![self]).centered()
|
|
|
|
}
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
pub fn centered_cross(mut self) -> ManagedWidget {
|
|
|
|
self.style.align_items = Some(AlignItems::Center);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn evenly_spaced(mut self) -> ManagedWidget {
|
|
|
|
self.style.justify_content = Some(JustifyContent::SpaceBetween);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-01-15 22:21:32 +03:00
|
|
|
// This one is really weird. percent_width should be LESS than the max_size_percent given to
|
|
|
|
// the overall Composite, otherwise weird things happen.
|
|
|
|
pub fn flex_wrap(mut self, ctx: &EventCtx, percent_width: usize) -> ManagedWidget {
|
2019-12-23 22:05:24 +03:00
|
|
|
self.style.size = Some(Size {
|
2020-01-15 22:21:32 +03:00
|
|
|
width: Dimension::Points(
|
|
|
|
(ctx.canvas.window_width * (percent_width as f64) / 100.0) as f32,
|
|
|
|
),
|
2019-12-23 22:05:24 +03:00
|
|
|
height: Dimension::Undefined,
|
|
|
|
});
|
2020-01-11 23:32:41 +03:00
|
|
|
self.style.flex_wrap = Some(FlexWrap::Wrap);
|
|
|
|
self.style.justify_content = Some(JustifyContent::SpaceAround);
|
|
|
|
self
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
|
|
|
|
pub fn bg(mut self, color: Color) -> ManagedWidget {
|
|
|
|
self.style.bg_color = Some(color);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-01-24 02:35:08 +03:00
|
|
|
// Callers have to adjust padding too, probably
|
|
|
|
pub fn outline(mut self, thickness: f64, color: Color) -> ManagedWidget {
|
|
|
|
self.style.outline = Some((thickness, color));
|
|
|
|
self
|
2020-01-20 01:18:38 +03:00
|
|
|
}
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
pub fn padding(mut self, pixels: usize) -> ManagedWidget {
|
|
|
|
self.style.padding = Some(Rect {
|
|
|
|
start: Dimension::Points(pixels as f32),
|
|
|
|
end: Dimension::Points(pixels as f32),
|
|
|
|
top: Dimension::Points(pixels as f32),
|
|
|
|
bottom: Dimension::Points(pixels as f32),
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn margin(mut self, pixels: usize) -> ManagedWidget {
|
|
|
|
self.style.margin = Some(Rect {
|
|
|
|
start: Dimension::Points(pixels as f32),
|
|
|
|
end: Dimension::Points(pixels as f32),
|
|
|
|
top: Dimension::Points(pixels as f32),
|
|
|
|
bottom: Dimension::Points(pixels as f32),
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
2020-01-11 22:54:42 +03:00
|
|
|
|
2020-02-04 03:13:40 +03:00
|
|
|
pub fn align_left(mut self) -> ManagedWidget {
|
|
|
|
self.style.margin = Some(Rect {
|
|
|
|
start: Dimension::Undefined,
|
|
|
|
end: Dimension::Auto,
|
|
|
|
top: Dimension::Undefined,
|
|
|
|
bottom: Dimension::Undefined,
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
2020-01-11 22:54:42 +03:00
|
|
|
pub fn align_right(mut self) -> ManagedWidget {
|
|
|
|
self.style.margin = Some(Rect {
|
|
|
|
start: Dimension::Auto,
|
|
|
|
end: Dimension::Undefined,
|
|
|
|
top: Dimension::Undefined,
|
|
|
|
bottom: Dimension::Undefined,
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
2020-02-04 03:13:40 +03:00
|
|
|
// This doesn't count against the entire container
|
|
|
|
pub fn align_vert_center(mut self) -> ManagedWidget {
|
|
|
|
self.style.margin = Some(Rect {
|
|
|
|
start: Dimension::Undefined,
|
|
|
|
end: Dimension::Undefined,
|
|
|
|
top: Dimension::Auto,
|
|
|
|
bottom: Dimension::Auto,
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
2020-01-15 03:46:45 +03:00
|
|
|
|
|
|
|
fn abs(mut self, x: f64, y: f64) -> ManagedWidget {
|
|
|
|
self.style.position_type = Some(PositionType::Absolute);
|
|
|
|
self.style.position = Some(Rect {
|
|
|
|
start: Dimension::Points(x as f32),
|
|
|
|
end: Dimension::Undefined,
|
|
|
|
top: Dimension::Points(y as f32),
|
|
|
|
bottom: Dimension::Undefined,
|
|
|
|
});
|
|
|
|
self
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convenient?? constructors
|
|
|
|
impl ManagedWidget {
|
|
|
|
fn new(widget: WidgetType) -> ManagedWidget {
|
|
|
|
ManagedWidget {
|
|
|
|
widget,
|
|
|
|
style: LayoutStyle {
|
|
|
|
bg_color: None,
|
2020-01-24 02:35:08 +03:00
|
|
|
outline: None,
|
2019-12-21 02:56:04 +03:00
|
|
|
align_items: None,
|
|
|
|
justify_content: None,
|
|
|
|
flex_wrap: None,
|
2019-12-23 22:05:24 +03:00
|
|
|
size: None,
|
2019-12-21 02:56:04 +03:00
|
|
|
padding: None,
|
|
|
|
margin: None,
|
2020-01-15 03:46:45 +03:00
|
|
|
position_type: None,
|
|
|
|
position: None,
|
2019-12-21 02:56:04 +03:00
|
|
|
},
|
|
|
|
rect: ScreenRectangle::placeholder(),
|
|
|
|
bg: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-07 10:08:17 +03:00
|
|
|
// TODO dupe apis!
|
2019-12-21 02:56:04 +03:00
|
|
|
pub fn draw_batch(ctx: &EventCtx, batch: GeomBatch) -> ManagedWidget {
|
2020-02-07 10:08:17 +03:00
|
|
|
JustDraw::wrap(ctx, batch)
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
2020-02-07 10:08:17 +03:00
|
|
|
pub(crate) fn just_draw(j: JustDraw) -> ManagedWidget {
|
2019-12-21 02:56:04 +03:00
|
|
|
ManagedWidget::new(WidgetType::Draw(j))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw_text(ctx: &EventCtx, txt: Text) -> ManagedWidget {
|
2020-02-07 10:08:17 +03:00
|
|
|
JustDraw::text(ctx, txt)
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw_svg(ctx: &EventCtx, filename: &str) -> ManagedWidget {
|
2020-02-07 10:08:17 +03:00
|
|
|
JustDraw::svg(ctx, filename)
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
2020-01-14 02:56:25 +03:00
|
|
|
// TODO Argh uncomposable APIs
|
|
|
|
pub fn draw_svg_transform(
|
|
|
|
ctx: &EventCtx,
|
|
|
|
filename: &str,
|
|
|
|
rewrite: RewriteColor,
|
|
|
|
) -> ManagedWidget {
|
2020-02-07 10:08:17 +03:00
|
|
|
JustDraw::svg_transform(ctx, filename, rewrite)
|
2020-01-14 02:56:25 +03:00
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
|
|
|
|
pub fn btn(btn: Button) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Btn(btn))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn slider(label: &str) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Slider(label.to_string()))
|
|
|
|
}
|
|
|
|
|
2020-01-02 20:35:41 +03:00
|
|
|
pub fn menu(label: &str) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Menu(label.to_string()))
|
|
|
|
}
|
|
|
|
|
2019-12-25 23:01:51 +03:00
|
|
|
pub fn filler(label: &str) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Filler(label.to_string()))
|
|
|
|
}
|
|
|
|
|
2019-12-21 21:43:26 +03:00
|
|
|
pub(crate) fn duration_plot(plot: Plot<Duration>) -> ManagedWidget {
|
2019-12-21 02:56:04 +03:00
|
|
|
ManagedWidget::new(WidgetType::DurationPlot(plot))
|
|
|
|
}
|
|
|
|
|
2019-12-21 21:43:26 +03:00
|
|
|
pub(crate) fn usize_plot(plot: Plot<usize>) -> ManagedWidget {
|
2019-12-21 02:56:04 +03:00
|
|
|
ManagedWidget::new(WidgetType::UsizePlot(plot))
|
|
|
|
}
|
|
|
|
|
2019-12-29 19:16:38 +03:00
|
|
|
pub(crate) fn histogram(histogram: Histogram) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Histogram(histogram))
|
|
|
|
}
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
pub fn row(widgets: Vec<ManagedWidget>) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Row(widgets))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn col(widgets: Vec<ManagedWidget>) -> ManagedWidget {
|
|
|
|
ManagedWidget::new(WidgetType::Column(widgets))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Internals
|
|
|
|
impl ManagedWidget {
|
|
|
|
fn event(
|
|
|
|
&mut self,
|
|
|
|
ctx: &mut EventCtx,
|
2019-12-24 01:33:43 +03:00
|
|
|
sliders: &mut HashMap<String, Slider>,
|
2020-01-02 20:35:41 +03:00
|
|
|
menus: &mut HashMap<String, Menu>,
|
2019-12-21 02:56:04 +03:00
|
|
|
) -> Option<Outcome> {
|
|
|
|
match self.widget {
|
|
|
|
WidgetType::Draw(_) => {}
|
|
|
|
WidgetType::Btn(ref mut btn) => {
|
|
|
|
btn.event(ctx);
|
|
|
|
if btn.clicked() {
|
|
|
|
return Some(Outcome::Clicked(btn.action.clone()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
WidgetType::Slider(ref name) => {
|
|
|
|
sliders.get_mut(name).unwrap().event(ctx);
|
|
|
|
}
|
2020-01-02 20:35:41 +03:00
|
|
|
WidgetType::Menu(ref name) => {
|
|
|
|
menus.get_mut(name).unwrap().event(ctx);
|
|
|
|
}
|
2019-12-29 19:16:38 +03:00
|
|
|
WidgetType::Filler(_)
|
|
|
|
| WidgetType::DurationPlot(_)
|
|
|
|
| WidgetType::UsizePlot(_)
|
|
|
|
| WidgetType::Histogram(_) => {}
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::Row(ref mut widgets) | WidgetType::Column(ref mut widgets) => {
|
|
|
|
for w in widgets {
|
2020-01-02 20:35:41 +03:00
|
|
|
if let Some(o) = w.event(ctx, sliders, menus) {
|
2019-12-21 02:56:04 +03:00
|
|
|
return Some(o);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2020-01-02 20:35:41 +03:00
|
|
|
fn draw(
|
|
|
|
&self,
|
|
|
|
g: &mut GfxCtx,
|
|
|
|
sliders: &HashMap<String, Slider>,
|
|
|
|
menus: &HashMap<String, Menu>,
|
|
|
|
) {
|
2019-12-21 02:56:04 +03:00
|
|
|
if let Some(ref bg) = self.bg {
|
2019-12-22 21:52:41 +03:00
|
|
|
bg.redraw(ScreenPt::new(self.rect.x1, self.rect.y1), g);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
match self.widget {
|
|
|
|
WidgetType::Draw(ref j) => j.draw(g),
|
|
|
|
WidgetType::Btn(ref btn) => btn.draw(g),
|
2020-01-15 03:50:26 +03:00
|
|
|
WidgetType::Slider(ref name) => {
|
|
|
|
if name != "horiz scrollbar" && name != "vert scrollbar" {
|
|
|
|
sliders[name].draw(g);
|
|
|
|
}
|
|
|
|
}
|
2020-01-02 20:35:41 +03:00
|
|
|
WidgetType::Menu(ref name) => menus[name].draw(g),
|
2019-12-25 23:01:51 +03:00
|
|
|
WidgetType::Filler(_) => {}
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::DurationPlot(ref plot) => plot.draw(g),
|
|
|
|
WidgetType::UsizePlot(ref plot) => plot.draw(g),
|
2019-12-29 19:16:38 +03:00
|
|
|
WidgetType::Histogram(ref hgram) => hgram.draw(g),
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::Row(ref widgets) | WidgetType::Column(ref widgets) => {
|
|
|
|
for w in widgets {
|
2020-01-02 20:35:41 +03:00
|
|
|
w.draw(g, sliders, menus);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Populate a flattened list of Nodes, matching the traversal order
|
|
|
|
fn get_flexbox(
|
|
|
|
&self,
|
|
|
|
parent: Node,
|
2019-12-24 01:33:43 +03:00
|
|
|
sliders: &HashMap<String, Slider>,
|
2020-01-02 20:35:41 +03:00
|
|
|
menus: &HashMap<String, Menu>,
|
2019-12-25 23:01:51 +03:00
|
|
|
fillers: &HashMap<String, Filler>,
|
2019-12-21 02:56:04 +03:00
|
|
|
stretch: &mut Stretch,
|
|
|
|
nodes: &mut Vec<Node>,
|
|
|
|
) {
|
|
|
|
// TODO Can I use | in the match and "cast" to Widget?
|
|
|
|
let widget: &dyn Widget = match self.widget {
|
|
|
|
WidgetType::Draw(ref widget) => widget,
|
|
|
|
WidgetType::Btn(ref widget) => widget,
|
2019-12-24 01:33:43 +03:00
|
|
|
WidgetType::Slider(ref name) => &sliders[name],
|
2020-01-02 20:35:41 +03:00
|
|
|
WidgetType::Menu(ref name) => &menus[name],
|
2019-12-25 23:01:51 +03:00
|
|
|
WidgetType::Filler(ref name) => &fillers[name],
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::DurationPlot(ref widget) => widget,
|
|
|
|
WidgetType::UsizePlot(ref widget) => widget,
|
2019-12-29 19:16:38 +03:00
|
|
|
WidgetType::Histogram(ref widget) => widget,
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::Row(ref widgets) => {
|
|
|
|
let mut style = Style {
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
self.style.apply(&mut style);
|
|
|
|
let row = stretch.new_node(style, Vec::new()).unwrap();
|
|
|
|
nodes.push(row);
|
|
|
|
for widget in widgets {
|
2020-01-02 20:35:41 +03:00
|
|
|
widget.get_flexbox(row, sliders, menus, fillers, stretch, nodes);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
stretch.add_child(parent, row).unwrap();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
WidgetType::Column(ref widgets) => {
|
|
|
|
let mut style = Style {
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
self.style.apply(&mut style);
|
|
|
|
let col = stretch.new_node(style, Vec::new()).unwrap();
|
|
|
|
nodes.push(col);
|
|
|
|
for widget in widgets {
|
2020-01-02 20:35:41 +03:00
|
|
|
widget.get_flexbox(col, sliders, menus, fillers, stretch, nodes);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
stretch.add_child(parent, col).unwrap();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let dims = widget.get_dims();
|
|
|
|
let mut style = Style {
|
|
|
|
size: Size {
|
|
|
|
width: Dimension::Points(dims.width as f32),
|
|
|
|
height: Dimension::Points(dims.height as f32),
|
|
|
|
},
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
self.style.apply(&mut style);
|
|
|
|
let node = stretch.new_node(style, Vec::new()).unwrap();
|
|
|
|
stretch.add_child(parent, node).unwrap();
|
|
|
|
nodes.push(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn apply_flexbox(
|
|
|
|
&mut self,
|
2019-12-24 01:33:43 +03:00
|
|
|
sliders: &mut HashMap<String, Slider>,
|
2020-01-02 20:35:41 +03:00
|
|
|
menus: &mut HashMap<String, Menu>,
|
2019-12-25 23:01:51 +03:00
|
|
|
fillers: &mut HashMap<String, Filler>,
|
2019-12-21 02:56:04 +03:00
|
|
|
stretch: &Stretch,
|
|
|
|
nodes: &mut Vec<Node>,
|
|
|
|
dx: f64,
|
|
|
|
dy: f64,
|
2020-01-15 02:47:35 +03:00
|
|
|
scroll_offset: (f64, f64),
|
2019-12-21 02:56:04 +03:00
|
|
|
ctx: &EventCtx,
|
|
|
|
) {
|
|
|
|
let result = stretch.layout(nodes.pop().unwrap()).unwrap();
|
|
|
|
let x: f64 = result.location.x.into();
|
|
|
|
let y: f64 = result.location.y.into();
|
|
|
|
let width: f64 = result.size.width.into();
|
|
|
|
let height: f64 = result.size.height.into();
|
2019-12-24 02:00:22 +03:00
|
|
|
let top_left = match self.widget {
|
|
|
|
WidgetType::Slider(ref name) => {
|
2020-01-15 03:46:45 +03:00
|
|
|
// Don't scroll the scrollbars
|
|
|
|
if name == "horiz scrollbar" || name == "vert scrollbar" {
|
|
|
|
ScreenPt::new(x, y)
|
2019-12-24 02:00:22 +03:00
|
|
|
} else {
|
2020-01-15 02:47:35 +03:00
|
|
|
ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1)
|
2019-12-24 02:00:22 +03:00
|
|
|
}
|
|
|
|
}
|
2020-01-15 02:47:35 +03:00
|
|
|
_ => ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1),
|
2019-12-24 02:00:22 +03:00
|
|
|
};
|
|
|
|
self.rect = ScreenRectangle::top_left(top_left, ScreenDims::new(width, height));
|
2020-01-20 01:18:38 +03:00
|
|
|
|
|
|
|
// Assume widgets don't dynamically change, so we just upload the background once.
|
2020-01-24 02:35:08 +03:00
|
|
|
if self.bg.is_none() && (self.style.bg_color.is_some() || self.style.outline.is_some()) {
|
2020-01-20 01:18:38 +03:00
|
|
|
let mut batch = GeomBatch::new();
|
|
|
|
if let Some(c) = self.style.bg_color {
|
2020-01-20 23:36:27 +03:00
|
|
|
batch.push(c, Polygon::rounded_rectangle(width, height, 5.0));
|
2020-01-20 01:18:38 +03:00
|
|
|
}
|
2020-01-24 02:35:08 +03:00
|
|
|
if let Some((thickness, c)) = self.style.outline {
|
2020-01-20 01:18:38 +03:00
|
|
|
batch.push(
|
|
|
|
c,
|
2020-01-20 23:36:27 +03:00
|
|
|
Polygon::rounded_rectangle(width, height, 5.0)
|
2020-01-24 02:35:08 +03:00
|
|
|
.to_outline(Distance::meters(thickness)),
|
2020-01-20 01:18:38 +03:00
|
|
|
);
|
2019-12-22 21:52:41 +03:00
|
|
|
}
|
2020-01-20 01:18:38 +03:00
|
|
|
self.bg = Some(DrawBoth::new(ctx, batch, Vec::new()));
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
match self.widget {
|
|
|
|
WidgetType::Draw(ref mut widget) => {
|
2019-12-24 02:00:22 +03:00
|
|
|
widget.set_pos(top_left);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
WidgetType::Btn(ref mut widget) => {
|
2019-12-24 02:00:22 +03:00
|
|
|
widget.set_pos(top_left);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
WidgetType::Slider(ref name) => {
|
2019-12-24 02:00:22 +03:00
|
|
|
sliders.get_mut(name).unwrap().set_pos(top_left);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
2020-01-02 20:35:41 +03:00
|
|
|
WidgetType::Menu(ref name) => {
|
|
|
|
menus.get_mut(name).unwrap().set_pos(top_left);
|
|
|
|
}
|
2019-12-25 23:01:51 +03:00
|
|
|
WidgetType::Filler(ref name) => {
|
|
|
|
fillers.get_mut(name).unwrap().set_pos(top_left);
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::DurationPlot(ref mut widget) => {
|
2019-12-24 02:00:22 +03:00
|
|
|
widget.set_pos(top_left);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
WidgetType::UsizePlot(ref mut widget) => {
|
2019-12-24 02:00:22 +03:00
|
|
|
widget.set_pos(top_left);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
2019-12-29 19:16:38 +03:00
|
|
|
WidgetType::Histogram(ref mut widget) => {
|
|
|
|
widget.set_pos(top_left);
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::Row(ref mut widgets) => {
|
|
|
|
// layout() doesn't return absolute position; it's relative to the container.
|
|
|
|
for widget in widgets {
|
2019-12-24 02:00:22 +03:00
|
|
|
widget.apply_flexbox(
|
|
|
|
sliders,
|
2020-01-02 20:35:41 +03:00
|
|
|
menus,
|
2019-12-25 23:01:51 +03:00
|
|
|
fillers,
|
2019-12-24 02:00:22 +03:00
|
|
|
stretch,
|
|
|
|
nodes,
|
|
|
|
x + dx,
|
|
|
|
y + dy,
|
2020-01-15 02:47:35 +03:00
|
|
|
scroll_offset,
|
2019-12-24 02:00:22 +03:00
|
|
|
ctx,
|
|
|
|
);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
WidgetType::Column(ref mut widgets) => {
|
|
|
|
for widget in widgets {
|
2019-12-24 02:00:22 +03:00
|
|
|
widget.apply_flexbox(
|
|
|
|
sliders,
|
2020-01-02 20:35:41 +03:00
|
|
|
menus,
|
2019-12-25 23:01:51 +03:00
|
|
|
fillers,
|
2019-12-24 02:00:22 +03:00
|
|
|
stretch,
|
|
|
|
nodes,
|
|
|
|
x + dx,
|
|
|
|
y + dy,
|
2020-01-15 02:47:35 +03:00
|
|
|
scroll_offset,
|
2019-12-24 02:00:22 +03:00
|
|
|
ctx,
|
|
|
|
);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_all_click_actions(&self, actions: &mut HashSet<String>) {
|
|
|
|
match self.widget {
|
|
|
|
WidgetType::Draw(_)
|
|
|
|
| WidgetType::Slider(_)
|
2020-01-02 20:35:41 +03:00
|
|
|
| WidgetType::Menu(_)
|
2019-12-25 23:01:51 +03:00
|
|
|
| WidgetType::Filler(_)
|
2019-12-21 02:56:04 +03:00
|
|
|
| WidgetType::DurationPlot(_)
|
|
|
|
| WidgetType::UsizePlot(_) => {}
|
2019-12-29 19:16:38 +03:00
|
|
|
WidgetType::Histogram(_) => {}
|
2019-12-21 02:56:04 +03:00
|
|
|
WidgetType::Btn(ref btn) => {
|
|
|
|
if actions.contains(&btn.action) {
|
|
|
|
panic!(
|
|
|
|
"Two buttons in one Composite both use action {}",
|
|
|
|
btn.action
|
|
|
|
);
|
|
|
|
}
|
|
|
|
actions.insert(btn.action.clone());
|
|
|
|
}
|
|
|
|
WidgetType::Row(ref widgets) | WidgetType::Column(ref widgets) => {
|
|
|
|
for w in widgets {
|
|
|
|
w.get_all_click_actions(actions);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-01-22 02:01:10 +03:00
|
|
|
|
2020-02-01 22:39:48 +03:00
|
|
|
pub fn has_name(&self, name: &str) -> bool {
|
|
|
|
self.rect_of(name).is_some()
|
|
|
|
}
|
|
|
|
|
2020-01-30 02:59:56 +03:00
|
|
|
fn rect_of(&self, name: &str) -> Option<&ScreenRectangle> {
|
2020-01-22 02:01:10 +03:00
|
|
|
let found = match self.widget {
|
|
|
|
WidgetType::Draw(_) => false,
|
|
|
|
WidgetType::Btn(ref btn) => btn.action == name,
|
|
|
|
WidgetType::Slider(ref n) => n == name,
|
|
|
|
WidgetType::Menu(ref n) => n == name,
|
|
|
|
WidgetType::Filler(ref n) => n == name,
|
|
|
|
WidgetType::DurationPlot(_) => false,
|
|
|
|
WidgetType::UsizePlot(_) => false,
|
|
|
|
WidgetType::Histogram(_) => false,
|
|
|
|
WidgetType::Row(ref widgets) | WidgetType::Column(ref widgets) => {
|
|
|
|
for widget in widgets {
|
2020-01-30 02:59:56 +03:00
|
|
|
if let Some(rect) = widget.rect_of(name) {
|
|
|
|
return Some(rect);
|
2020-01-22 02:01:10 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if found {
|
2020-01-30 02:59:56 +03:00
|
|
|
Some(&self.rect)
|
2020-01-22 02:01:10 +03:00
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
2020-02-04 03:13:40 +03:00
|
|
|
enum Dims {
|
|
|
|
MaxPercent(f64, f64),
|
|
|
|
ExactPercent(f64, f64),
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:08:39 +03:00
|
|
|
pub struct CompositeBuilder {
|
|
|
|
top_level: ManagedWidget,
|
2020-01-15 21:33:08 +03:00
|
|
|
|
2020-01-07 21:08:39 +03:00
|
|
|
sliders: HashMap<String, Slider>,
|
|
|
|
menus: HashMap<String, Menu>,
|
|
|
|
fillers: HashMap<String, Filler>,
|
2020-01-15 21:33:08 +03:00
|
|
|
|
|
|
|
horiz: HorizontalAlignment,
|
|
|
|
vert: VerticalAlignment,
|
2020-02-04 03:13:40 +03:00
|
|
|
dims: Dims,
|
2020-01-07 21:08:39 +03:00
|
|
|
}
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
pub struct Composite {
|
|
|
|
top_level: ManagedWidget,
|
2019-12-22 21:26:48 +03:00
|
|
|
|
2019-12-24 01:33:43 +03:00
|
|
|
sliders: HashMap<String, Slider>,
|
2020-01-02 20:35:41 +03:00
|
|
|
menus: HashMap<String, Menu>,
|
2019-12-25 23:01:51 +03:00
|
|
|
fillers: HashMap<String, Filler>,
|
2019-12-24 01:33:43 +03:00
|
|
|
|
2020-01-15 21:33:08 +03:00
|
|
|
horiz: HorizontalAlignment,
|
|
|
|
vert: VerticalAlignment,
|
2020-02-04 03:13:40 +03:00
|
|
|
dims: Dims,
|
2020-01-15 21:33:08 +03:00
|
|
|
|
2020-01-15 02:47:35 +03:00
|
|
|
scrollable_x: bool,
|
|
|
|
scrollable_y: bool,
|
|
|
|
contents_dims: ScreenDims,
|
|
|
|
container_dims: ScreenDims,
|
2020-01-15 02:18:27 +03:00
|
|
|
clip_rect: Option<ScreenRectangle>,
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub enum Outcome {
|
|
|
|
Clicked(String),
|
|
|
|
}
|
|
|
|
|
2019-12-22 21:26:48 +03:00
|
|
|
const SCROLL_SPEED: f64 = 5.0;
|
|
|
|
|
2020-01-02 20:35:41 +03:00
|
|
|
// TODO These APIs aren't composable. Need a builer pattern or ideally, to scrape all the special
|
|
|
|
// objects from the tree.
|
2019-12-21 02:56:04 +03:00
|
|
|
impl Composite {
|
2020-01-07 21:08:39 +03:00
|
|
|
pub fn new(top_level: ManagedWidget) -> CompositeBuilder {
|
|
|
|
CompositeBuilder {
|
2019-12-21 02:56:04 +03:00
|
|
|
top_level,
|
2020-01-15 21:33:08 +03:00
|
|
|
|
2019-12-24 01:33:43 +03:00
|
|
|
sliders: HashMap::new(),
|
2020-01-02 20:35:41 +03:00
|
|
|
menus: HashMap::new(),
|
2019-12-25 23:01:51 +03:00
|
|
|
fillers: HashMap::new(),
|
2020-01-15 21:33:08 +03:00
|
|
|
|
|
|
|
horiz: HorizontalAlignment::Center,
|
|
|
|
vert: VerticalAlignment::Center,
|
2020-02-04 03:13:40 +03:00
|
|
|
dims: Dims::MaxPercent(1.0, 1.0),
|
2019-12-24 01:33:43 +03:00
|
|
|
}
|
2019-12-22 21:26:48 +03:00
|
|
|
}
|
|
|
|
|
2019-12-24 01:33:43 +03:00
|
|
|
fn recompute_layout(&mut self, ctx: &EventCtx) {
|
2019-12-21 02:56:04 +03:00
|
|
|
let mut stretch = Stretch::new();
|
|
|
|
let root = stretch
|
|
|
|
.new_node(
|
2020-01-11 23:59:27 +03:00
|
|
|
Style {
|
|
|
|
..Default::default()
|
2019-12-21 02:56:04 +03:00
|
|
|
},
|
|
|
|
Vec::new(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let mut nodes = vec![];
|
2020-01-02 20:35:41 +03:00
|
|
|
self.top_level.get_flexbox(
|
|
|
|
root,
|
|
|
|
&self.sliders,
|
|
|
|
&self.menus,
|
|
|
|
&self.fillers,
|
|
|
|
&mut stretch,
|
|
|
|
&mut nodes,
|
|
|
|
);
|
2019-12-21 02:56:04 +03:00
|
|
|
nodes.reverse();
|
|
|
|
|
2020-01-15 02:57:21 +03:00
|
|
|
// TODO Express more simply. Constraining this seems useless.
|
2020-01-15 02:18:27 +03:00
|
|
|
let container_size = Size {
|
2020-01-15 02:57:21 +03:00
|
|
|
width: Number::Undefined,
|
|
|
|
height: Number::Undefined,
|
2020-01-15 02:18:27 +03:00
|
|
|
};
|
|
|
|
stretch.compute_layout(root, container_size).unwrap();
|
2020-01-15 22:21:32 +03:00
|
|
|
|
|
|
|
// TODO I'm so confused why these 2 are acting differently. :(
|
|
|
|
let effective_dims = if self.scrollable_x || self.scrollable_y {
|
|
|
|
self.container_dims
|
|
|
|
} else {
|
|
|
|
let result = stretch.layout(root).unwrap();
|
|
|
|
ScreenDims::new(result.size.width.into(), result.size.height.into())
|
|
|
|
};
|
|
|
|
let top_left = ctx
|
|
|
|
.canvas
|
|
|
|
.align_window(effective_dims, self.horiz, self.vert);
|
2020-01-15 02:57:21 +03:00
|
|
|
let offset = self.scroll_offset();
|
2019-12-21 02:56:04 +03:00
|
|
|
self.top_level.apply_flexbox(
|
2019-12-24 01:33:43 +03:00
|
|
|
&mut self.sliders,
|
2020-01-02 20:35:41 +03:00
|
|
|
&mut self.menus,
|
2019-12-25 23:01:51 +03:00
|
|
|
&mut self.fillers,
|
2019-12-21 02:56:04 +03:00
|
|
|
&stretch,
|
|
|
|
&mut nodes,
|
|
|
|
top_left.x,
|
2019-12-24 02:00:22 +03:00
|
|
|
top_left.y,
|
|
|
|
offset,
|
2019-12-21 02:56:04 +03:00
|
|
|
ctx,
|
|
|
|
);
|
|
|
|
assert!(nodes.is_empty());
|
|
|
|
}
|
|
|
|
|
2020-01-15 02:57:21 +03:00
|
|
|
fn scroll_offset(&self) -> (f64, f64) {
|
2020-01-15 02:47:35 +03:00
|
|
|
let x = if self.scrollable_x {
|
|
|
|
self.slider("horiz scrollbar").get_percent()
|
|
|
|
* (self.contents_dims.width - self.container_dims.width).max(0.0)
|
2019-12-24 02:00:22 +03:00
|
|
|
} else {
|
|
|
|
0.0
|
2020-01-15 02:47:35 +03:00
|
|
|
};
|
|
|
|
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)
|
2019-12-24 02:00:22 +03:00
|
|
|
}
|
|
|
|
|
2020-01-15 02:47:35 +03:00
|
|
|
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 {
|
|
|
|
self.mut_slider("horiz scrollbar").set_percent(ctx, 0.0);
|
|
|
|
} else {
|
|
|
|
self.mut_slider("horiz scrollbar")
|
2020-01-26 01:25:47 +03:00
|
|
|
.set_percent(ctx, abstutil::clamp(offset.0, 0.0, max) / max);
|
2020-01-15 02:47:35 +03:00
|
|
|
}
|
2020-01-10 21:28:13 +03:00
|
|
|
}
|
2020-01-15 02:47:35 +03:00
|
|
|
if self.scrollable_y {
|
|
|
|
changed = true;
|
|
|
|
let max = (self.contents_dims.height - self.container_dims.height).max(0.0);
|
|
|
|
if max == 0.0 {
|
|
|
|
self.mut_slider("vert scrollbar").set_percent(ctx, 0.0);
|
|
|
|
} else {
|
|
|
|
self.mut_slider("vert scrollbar")
|
2020-01-26 01:25:47 +03:00
|
|
|
.set_percent(ctx, abstutil::clamp(offset.1, 0.0, max) / max);
|
2020-01-15 02:47:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if changed {
|
|
|
|
self.recompute_layout(ctx);
|
2019-12-24 02:00:22 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
pub fn event(&mut self, ctx: &mut EventCtx) -> Option<Outcome> {
|
2020-01-15 02:47:35 +03:00
|
|
|
if (self.scrollable_x || self.scrollable_y)
|
2020-01-18 01:11:07 +03:00
|
|
|
&& ctx
|
|
|
|
.canvas
|
|
|
|
.get_cursor_in_screen_space()
|
|
|
|
.map(|pt| self.top_level.rect.contains(pt))
|
|
|
|
.unwrap_or(false)
|
2019-12-22 21:26:48 +03:00
|
|
|
{
|
2020-01-15 02:47:35 +03:00
|
|
|
if let Some((dx, dy)) = ctx.input.get_mouse_scroll() {
|
|
|
|
let x_offset = if self.scrollable_x {
|
2020-01-26 01:25:47 +03:00
|
|
|
self.scroll_offset().0 + dx * SCROLL_SPEED
|
2020-01-15 02:47:35 +03:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
let y_offset = if self.scrollable_y {
|
2020-01-26 01:25:47 +03:00
|
|
|
self.scroll_offset().1 - dy * SCROLL_SPEED
|
2020-01-15 02:47:35 +03:00
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
self.set_scroll_offset(ctx, (x_offset, y_offset));
|
2019-12-22 21:26:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-21 02:56:04 +03:00
|
|
|
if ctx.input.is_window_resized() {
|
2019-12-24 01:33:43 +03:00
|
|
|
self.recompute_layout(ctx);
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
2019-12-22 21:26:48 +03:00
|
|
|
|
2020-01-15 02:57:21 +03:00
|
|
|
let before = self.scroll_offset();
|
2020-01-02 20:35:41 +03:00
|
|
|
let result = self
|
|
|
|
.top_level
|
|
|
|
.event(ctx, &mut self.sliders, &mut self.menus);
|
2020-01-15 02:57:21 +03:00
|
|
|
if self.scroll_offset() != before {
|
2019-12-24 02:00:22 +03:00
|
|
|
self.recompute_layout(ctx);
|
|
|
|
}
|
|
|
|
result
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw(&self, g: &mut GfxCtx) {
|
2020-01-18 00:58:33 +03:00
|
|
|
if let Some(ref rect) = self.clip_rect {
|
|
|
|
g.enable_clipping(rect.clone());
|
|
|
|
g.canvas.mark_covered_area(rect.clone());
|
|
|
|
} else {
|
|
|
|
g.canvas.mark_covered_area(self.top_level.rect.clone());
|
2020-01-15 02:18:27 +03:00
|
|
|
}
|
2020-02-04 03:13:40 +03:00
|
|
|
|
|
|
|
// Debugging
|
|
|
|
if false {
|
|
|
|
g.fork_screenspace();
|
|
|
|
g.draw_polygon(Color::RED.alpha(0.5), &self.top_level.rect.to_polygon());
|
|
|
|
|
|
|
|
let top_left = g
|
|
|
|
.canvas
|
|
|
|
.align_window(self.container_dims, self.horiz, self.vert);
|
|
|
|
g.draw_polygon(
|
|
|
|
Color::BLUE.alpha(0.5),
|
|
|
|
&Polygon::rectangle(self.container_dims.width, self.container_dims.height)
|
|
|
|
.translate(top_left.x, top_left.y),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
g.unfork();
|
|
|
|
|
2020-01-02 20:35:41 +03:00
|
|
|
self.top_level.draw(g, &self.sliders, &self.menus);
|
2020-01-15 02:47:35 +03:00
|
|
|
if self.scrollable_x || self.scrollable_y {
|
2020-01-15 02:18:27 +03:00
|
|
|
g.disable_clipping();
|
2020-01-15 03:50:26 +03:00
|
|
|
|
|
|
|
// Draw the scrollbars after clipping is disabled, because they actually live just
|
|
|
|
// outside the rectangle.
|
|
|
|
if self.scrollable_x {
|
|
|
|
self.sliders["horiz scrollbar"].draw(g);
|
|
|
|
}
|
|
|
|
if self.scrollable_y {
|
|
|
|
self.sliders["vert scrollbar"].draw(g);
|
|
|
|
}
|
2020-01-15 02:18:27 +03:00
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_all_click_actions(&self) -> HashSet<String> {
|
|
|
|
let mut actions = HashSet::new();
|
|
|
|
self.top_level.get_all_click_actions(&mut actions);
|
|
|
|
actions
|
|
|
|
}
|
|
|
|
|
2020-01-15 02:57:21 +03:00
|
|
|
pub fn preserve_scroll(&self) -> (f64, f64) {
|
|
|
|
self.scroll_offset()
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
|
|
|
|
2020-01-15 02:47:35 +03:00
|
|
|
pub fn restore_scroll(&mut self, ctx: &EventCtx, offset: (f64, f64)) {
|
|
|
|
self.set_scroll_offset(ctx, offset);
|
2019-12-24 01:33:43 +03:00
|
|
|
}
|
|
|
|
|
2020-01-30 02:59:56 +03:00
|
|
|
pub fn scroll_to_member(&mut self, ctx: &EventCtx, name: String) {
|
|
|
|
if let Some(rect) = self.top_level.rect_of(&name) {
|
|
|
|
let y1 = rect.y1;
|
|
|
|
self.set_scroll_offset(ctx, (0.0, y1));
|
|
|
|
} else {
|
|
|
|
panic!("Can't scroll_to_member of unknown {}", name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-24 01:33:43 +03:00
|
|
|
pub fn slider(&self, name: &str) -> &Slider {
|
|
|
|
&self.sliders[name]
|
|
|
|
}
|
|
|
|
pub fn mut_slider(&mut self, name: &str) -> &mut Slider {
|
|
|
|
self.sliders.get_mut(name).unwrap()
|
|
|
|
}
|
2019-12-25 23:01:51 +03:00
|
|
|
|
2020-01-02 20:35:41 +03:00
|
|
|
pub fn menu(&self, name: &str) -> &Menu {
|
|
|
|
&self.menus[name]
|
|
|
|
}
|
|
|
|
|
2019-12-25 23:01:51 +03:00
|
|
|
pub fn filler_rect(&self, name: &str) -> ScreenRectangle {
|
|
|
|
let f = &self.fillers[name];
|
|
|
|
ScreenRectangle::top_left(f.top_left, f.dims)
|
|
|
|
}
|
2020-01-22 02:01:10 +03:00
|
|
|
|
2020-02-05 00:25:53 +03:00
|
|
|
pub fn rect_of(&self, name: &str) -> &ScreenRectangle {
|
2020-01-30 02:59:56 +03:00
|
|
|
if let Some(rect) = self.top_level.rect_of(name) {
|
2020-02-05 00:25:53 +03:00
|
|
|
rect
|
2020-01-22 02:01:10 +03:00
|
|
|
} else {
|
|
|
|
panic!("Can't find center_of {}", name);
|
|
|
|
}
|
|
|
|
}
|
2020-02-05 00:25:53 +03:00
|
|
|
// TODO Deprecate
|
|
|
|
pub fn center_of(&self, name: &str) -> ScreenPt {
|
|
|
|
self.rect_of(name).center()
|
|
|
|
}
|
2020-01-22 02:01:10 +03:00
|
|
|
pub fn center_of_panel(&self) -> ScreenPt {
|
|
|
|
self.top_level.rect.center()
|
|
|
|
}
|
2020-01-31 01:38:26 +03:00
|
|
|
|
|
|
|
pub fn align_above(&mut self, ctx: &mut EventCtx, other: &Composite) {
|
2020-01-31 02:33:27 +03:00
|
|
|
// Small padding
|
2020-02-05 00:25:53 +03:00
|
|
|
self.vert = VerticalAlignment::Above(other.top_level.rect.y1 - 5.0);
|
2020-01-31 01:38:26 +03:00
|
|
|
self.recompute_layout(ctx);
|
|
|
|
}
|
2019-12-21 02:56:04 +03:00
|
|
|
}
|
2020-01-07 21:08:39 +03:00
|
|
|
|
|
|
|
impl CompositeBuilder {
|
|
|
|
pub fn build(self, ctx: &mut EventCtx) -> Composite {
|
|
|
|
let mut c = Composite {
|
|
|
|
top_level: self.top_level,
|
|
|
|
sliders: self.sliders,
|
|
|
|
menus: self.menus,
|
|
|
|
fillers: self.fillers,
|
2020-01-15 02:18:27 +03:00
|
|
|
|
2020-01-15 21:33:08 +03:00
|
|
|
horiz: self.horiz,
|
|
|
|
vert: self.vert,
|
2020-02-04 03:13:40 +03:00
|
|
|
dims: self.dims,
|
2020-01-15 21:33:08 +03:00
|
|
|
|
2020-01-15 02:47:35 +03:00
|
|
|
scrollable_x: false,
|
|
|
|
scrollable_y: false,
|
|
|
|
contents_dims: ScreenDims::new(0.0, 0.0),
|
|
|
|
container_dims: ScreenDims::new(0.0, 0.0),
|
2020-01-15 02:18:27 +03:00
|
|
|
clip_rect: None,
|
2020-01-07 21:08:39 +03:00
|
|
|
};
|
2020-02-04 03:13:40 +03:00
|
|
|
if let Dims::ExactPercent(w, h) = c.dims {
|
|
|
|
c.top_level.style.size = Some(Size {
|
|
|
|
width: Dimension::Points((w * ctx.canvas.window_width) as f32),
|
|
|
|
height: Dimension::Points((h * ctx.canvas.window_height) as f32),
|
|
|
|
});
|
|
|
|
}
|
2020-01-07 21:08:39 +03:00
|
|
|
c.recompute_layout(ctx);
|
2020-01-15 02:18:27 +03:00
|
|
|
|
2020-01-15 02:47:35 +03:00
|
|
|
c.contents_dims = ScreenDims::new(c.top_level.rect.width(), c.top_level.rect.height());
|
2020-02-04 03:13:40 +03:00
|
|
|
c.container_dims = match c.dims {
|
|
|
|
Dims::MaxPercent(w, h) => ScreenDims::new(
|
|
|
|
c.contents_dims.width.min(w * ctx.canvas.window_width),
|
|
|
|
c.contents_dims.height.min(h * ctx.canvas.window_height),
|
|
|
|
),
|
|
|
|
Dims::ExactPercent(w, h) => {
|
|
|
|
ScreenDims::new(w * ctx.canvas.window_width, h * ctx.canvas.window_height)
|
|
|
|
}
|
|
|
|
};
|
2020-01-15 02:18:27 +03:00
|
|
|
|
2020-01-15 21:19:21 +03:00
|
|
|
// If the panel fits without a scrollbar, don't add one.
|
2020-01-15 21:33:08 +03:00
|
|
|
let top_left = ctx.canvas.align_window(c.container_dims, c.horiz, c.vert);
|
2020-01-15 02:47:35 +03:00
|
|
|
if c.contents_dims.width > c.container_dims.width {
|
|
|
|
c.scrollable_x = true;
|
2020-01-07 21:08:39 +03:00
|
|
|
c.sliders.insert(
|
2020-01-15 02:47:35 +03:00
|
|
|
"horiz scrollbar".to_string(),
|
2020-01-15 23:07:25 +03:00
|
|
|
Slider::horizontal(
|
|
|
|
ctx,
|
|
|
|
c.container_dims.width,
|
|
|
|
c.container_dims.width * (c.container_dims.width / c.contents_dims.width),
|
|
|
|
),
|
2020-01-07 21:08:39 +03:00
|
|
|
);
|
2020-01-15 03:46:45 +03:00
|
|
|
c.top_level = ManagedWidget::col(vec![
|
|
|
|
c.top_level,
|
|
|
|
ManagedWidget::slider("horiz scrollbar")
|
|
|
|
.abs(top_left.x, top_left.y + c.container_dims.height),
|
|
|
|
]);
|
2020-01-15 02:47:35 +03:00
|
|
|
}
|
|
|
|
if c.contents_dims.height > c.container_dims.height {
|
|
|
|
c.scrollable_y = true;
|
|
|
|
c.sliders.insert(
|
|
|
|
"vert scrollbar".to_string(),
|
2020-01-15 23:07:25 +03:00
|
|
|
Slider::vertical(
|
|
|
|
ctx,
|
|
|
|
c.container_dims.height,
|
|
|
|
c.container_dims.height * (c.container_dims.height / c.contents_dims.height),
|
|
|
|
),
|
2020-01-15 02:47:35 +03:00
|
|
|
);
|
2020-01-15 03:46:45 +03:00
|
|
|
c.top_level = ManagedWidget::row(vec![
|
|
|
|
c.top_level,
|
|
|
|
ManagedWidget::slider("vert scrollbar")
|
|
|
|
.abs(top_left.x + c.container_dims.width, top_left.y),
|
|
|
|
]);
|
2020-01-15 02:47:35 +03:00
|
|
|
}
|
|
|
|
if c.scrollable_x || c.scrollable_y {
|
2020-01-07 21:08:39 +03:00
|
|
|
c.recompute_layout(ctx);
|
2020-01-15 03:50:26 +03:00
|
|
|
c.clip_rect = Some(ScreenRectangle::top_left(top_left, c.container_dims));
|
2020-01-07 21:08:39 +03:00
|
|
|
}
|
2020-01-15 02:47:35 +03:00
|
|
|
|
2020-01-24 04:11:29 +03:00
|
|
|
ctx.no_op_event(true, |ctx| assert!(c.event(ctx).is_none()));
|
2020-01-07 21:08:39 +03:00
|
|
|
c
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn aligned(
|
|
|
|
mut self,
|
|
|
|
horiz: HorizontalAlignment,
|
|
|
|
vert: VerticalAlignment,
|
|
|
|
) -> CompositeBuilder {
|
2020-01-15 21:33:08 +03:00
|
|
|
self.horiz = horiz;
|
|
|
|
self.vert = vert;
|
2020-01-07 21:08:39 +03:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-01-15 21:33:08 +03:00
|
|
|
pub fn max_size_percent(mut self, pct_width: usize, pct_height: usize) -> CompositeBuilder {
|
|
|
|
if pct_width == 100 && pct_height == 100 {
|
|
|
|
panic!("By default, Composites are capped at 100% of the screen. This is redundant.");
|
|
|
|
}
|
2020-02-04 03:13:40 +03:00
|
|
|
self.dims = Dims::MaxPercent((pct_width as f64) / 100.0, (pct_height as f64) / 100.0);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn exact_size_percent(mut self, pct_width: usize, pct_height: usize) -> CompositeBuilder {
|
|
|
|
self.dims = Dims::ExactPercent((pct_width as f64) / 100.0, (pct_height as f64) / 100.0);
|
2020-01-11 23:32:41 +03:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-01-07 21:08:39 +03:00
|
|
|
pub fn filler(mut self, name: &str, filler: Filler) -> CompositeBuilder {
|
|
|
|
self.fillers.insert(name.to_string(), filler);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
pub fn slider(mut self, name: &str, slider: Slider) -> CompositeBuilder {
|
|
|
|
self.sliders.insert(name.to_string(), slider);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
pub fn menu(mut self, name: &str, menu: Menu) -> CompositeBuilder {
|
|
|
|
self.menus.insert(name.to_string(), menu);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|