mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +03:00
WIP: spiked tabs implementation for widgetry demo
TODO: - tooltips (or is that just a responsibility of the button builder?) - widgetry color scheme should use day-theme - pick nice tab colors - start applying! - jump in time - info panel - "more data" - fix width to match widest? Or maybe width modes?
This commit is contained in:
parent
bf52a84956
commit
ab787a5cd1
@ -63,6 +63,7 @@ pub use crate::widgets::scatter_plot::ScatterPlot;
|
||||
pub use crate::widgets::slider::Slider;
|
||||
pub use crate::widgets::spinner::Spinner;
|
||||
pub use crate::widgets::table;
|
||||
pub use crate::widgets::tabs::TabController;
|
||||
pub(crate) use crate::widgets::text_box::TextBox;
|
||||
pub use crate::widgets::toggle::Toggle;
|
||||
pub use crate::widgets::{
|
||||
|
@ -33,6 +33,7 @@ pub mod scatter_plot;
|
||||
pub mod slider;
|
||||
pub mod spinner;
|
||||
pub mod table;
|
||||
pub mod tabs;
|
||||
pub mod text_box;
|
||||
pub mod toggle;
|
||||
|
||||
|
@ -437,6 +437,11 @@ impl Panel {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_container_content(&mut self, name: &str, widget: &mut Widget) {
|
||||
let old_container: &mut Container = self.find_mut(name);
|
||||
std::mem::swap(&mut old_container.members[0], widget);
|
||||
}
|
||||
|
||||
pub fn rect_of(&self, name: &str) -> &ScreenRectangle {
|
||||
&self.top_level.find(name).unwrap().rect
|
||||
}
|
||||
|
128
widgetry/src/widgets/tabs.rs
Normal file
128
widgetry/src/widgets/tabs.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use crate::{ButtonBuilder, EventCtx, Panel, Widget};
|
||||
|
||||
struct Tab {
|
||||
tab_id: String,
|
||||
bar_item: ButtonBuilder<'static, 'static>,
|
||||
content: Widget,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
fn new(tab_id: String, bar_item: ButtonBuilder<'static, 'static>, content: Widget) -> Self {
|
||||
Self {
|
||||
tab_id,
|
||||
bar_item,
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_bar_item_widget(&self, ctx: &EventCtx, active: bool) -> Widget {
|
||||
self.bar_item
|
||||
.clone()
|
||||
.disabled(active)
|
||||
.build_widget(ctx, &self.tab_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TabController {
|
||||
id: String,
|
||||
tabs: Vec<Tab>,
|
||||
active_child: usize,
|
||||
}
|
||||
|
||||
impl TabController {
|
||||
pub fn new(
|
||||
id: String,
|
||||
initial_bar_item: ButtonBuilder<'static, 'static>,
|
||||
initial_content: Widget,
|
||||
) -> Self {
|
||||
let mut tc = Self {
|
||||
id,
|
||||
tabs: vec![],
|
||||
active_child: 0,
|
||||
};
|
||||
tc.push_tab(initial_bar_item, initial_content);
|
||||
|
||||
tc
|
||||
}
|
||||
|
||||
/// Add a new tab.
|
||||
///
|
||||
/// `bar_item`: The button shown in the tab bar
|
||||
/// `content`: The content shown when this tab's `bar_item` is clicked
|
||||
pub fn push_tab(&mut self, bar_item: ButtonBuilder<'static, 'static>, content: Widget) {
|
||||
let tab_id = self.tab_id(self.tabs.len() + 1);
|
||||
let tab = Tab::new(tab_id, bar_item, content);
|
||||
self.tabs.push(tab);
|
||||
}
|
||||
|
||||
/// A widget containing the tab bar and a content pane with the currently active tab.
|
||||
pub fn build_widget(&mut self, ctx: &EventCtx) -> Widget {
|
||||
Widget::col(vec![
|
||||
self.build_bar_items(ctx),
|
||||
self.pop_active_content()
|
||||
.container()
|
||||
.padding(16)
|
||||
.bg(ctx.style().section_bg)
|
||||
.named(self.active_content_id()),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn handle_action(&mut self, ctx: &EventCtx, action: &str, panel: &mut Panel) -> bool {
|
||||
if !action.starts_with(&self.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tab_idx = self
|
||||
.tabs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_idx, tab)| &tab.tab_id == action)
|
||||
.expect(&format!("invalid tab id: {}", action))
|
||||
.0;
|
||||
self.activate_tab(ctx, tab_idx, panel);
|
||||
true
|
||||
}
|
||||
|
||||
fn active_content_id(&self) -> String {
|
||||
format!("{}_active_content", self.id)
|
||||
}
|
||||
|
||||
fn bar_items_id(&self) -> String {
|
||||
format!("{}_bar_items", self.id)
|
||||
}
|
||||
|
||||
fn tab_id(&self, tab_index: usize) -> String {
|
||||
format!("{}_tab_{}", self.id, tab_index)
|
||||
}
|
||||
|
||||
fn pop_active_content(&mut self) -> Widget {
|
||||
let mut tmp = Widget::nothing();
|
||||
std::mem::swap(&mut self.tabs[self.active_child].content, &mut tmp);
|
||||
tmp
|
||||
}
|
||||
|
||||
fn build_bar_items(&self, ctx: &EventCtx) -> Widget {
|
||||
let bar_items = self
|
||||
.tabs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, tab)| tab.build_bar_item_widget(ctx, idx == self.active_child))
|
||||
.collect();
|
||||
Widget::row(bar_items)
|
||||
.container()
|
||||
.bg(ctx.style().section_bg)
|
||||
.named(self.bar_items_id())
|
||||
}
|
||||
|
||||
fn activate_tab(&mut self, ctx: &EventCtx, tab_idx: usize, panel: &mut Panel) {
|
||||
let old_idx = self.active_child;
|
||||
self.active_child = tab_idx;
|
||||
|
||||
let mut bar_items = self.build_bar_items(ctx);
|
||||
panel.swap_container_content(&self.bar_items_id(), &mut bar_items);
|
||||
|
||||
let mut content = self.pop_active_content();
|
||||
panel.swap_container_content(&self.active_content_id(), &mut content);
|
||||
self.tabs[old_idx].content = content;
|
||||
}
|
||||
}
|
@ -7,8 +7,8 @@ use geom::{Angle, Duration, Percent, Polygon, Pt2D, Time};
|
||||
use widgetry::{
|
||||
lctrl, Choice, Color, ContentMode, Drawable, EventCtx, Fill, GeomBatch, GfxCtx,
|
||||
HorizontalAlignment, Image, Key, Line, LinePlot, Outcome, Panel, PersistentSplit, PlotOptions,
|
||||
ScreenDims, Series, SharedAppState, State, Text, TextExt, Texture, Toggle, Transition,
|
||||
UpdateType, VerticalAlignment, Widget,
|
||||
ScreenDims, Series, SharedAppState, State, TabController, Text, TextExt, Texture, Toggle,
|
||||
Transition, UpdateType, VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
pub fn main() {
|
||||
@ -36,16 +36,19 @@ struct Demo {
|
||||
scrollable_canvas: Drawable,
|
||||
texture_demo: Drawable,
|
||||
elapsed: Duration,
|
||||
tabs: TabController,
|
||||
}
|
||||
|
||||
impl Demo {
|
||||
fn new(ctx: &mut EventCtx) -> Demo {
|
||||
Demo {
|
||||
controls: make_controls(ctx),
|
||||
fn new(ctx: &mut EventCtx) -> Self {
|
||||
let mut tabs = make_tabs(ctx);
|
||||
Self {
|
||||
controls: make_controls(ctx, &mut tabs),
|
||||
timeseries_panel: None,
|
||||
scrollable_canvas: setup_scrollable_canvas(ctx),
|
||||
texture_demo: setup_texture_demo(ctx, Texture::SAND, Texture::CACTUS),
|
||||
elapsed: Duration::ZERO,
|
||||
tabs: tabs,
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,7 +166,9 @@ impl State<App> for Demo {
|
||||
self.texture_demo = setup_texture_demo(ctx, bg_texture, fg_texture);
|
||||
}
|
||||
action => {
|
||||
if action.contains("btn_") {
|
||||
if self.tabs.handle_action(ctx, action, &mut self.controls) {
|
||||
// If true, tab already handled the action internally
|
||||
} else if action.contains("btn_") {
|
||||
log::info!("clicked button: {:?}", action);
|
||||
} else {
|
||||
unimplemented!("clicked: {:?}", x);
|
||||
@ -302,14 +307,11 @@ fn setup_scrollable_canvas(ctx: &mut EventCtx) -> Drawable {
|
||||
batch.upload(ctx)
|
||||
}
|
||||
|
||||
fn make_controls(ctx: &mut EventCtx) -> Panel {
|
||||
fn make_tabs(ctx: &mut EventCtx) -> TabController {
|
||||
let style = ctx.style();
|
||||
Panel::new(Widget::col(vec![
|
||||
Text::from_multiline(vec![
|
||||
Line("widgetry demo").big_heading_styled(),
|
||||
Line("Click and drag the background to pan, use touchpad or scroll wheel to zoom"),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
|
||||
let gallery_bar_item = style.btn_tab.text("Component Gallery");
|
||||
let gallery_content = Widget::col(vec![
|
||||
Text::from(Line("Text").big_heading_styled().size(18)).into_widget(ctx),
|
||||
Text::from_all(vec![
|
||||
Line("You can "),
|
||||
@ -320,7 +322,6 @@ fn make_controls(ctx: &mut EventCtx) -> Panel {
|
||||
.bg(Color::PURPLE)
|
||||
.into_widget(ctx),
|
||||
// Button Style Gallery
|
||||
// TODO might be nice to have this in separate tabs or something.
|
||||
Text::from(Line("Buttons").big_heading_styled().size(18)).into_widget(ctx),
|
||||
Widget::row(vec![
|
||||
style
|
||||
@ -411,6 +412,96 @@ fn make_controls(ctx: &mut EventCtx) -> Panel {
|
||||
]),
|
||||
Text::from(Line("Spinner").big_heading_styled().size(18)).into_widget(ctx),
|
||||
widgetry::Spinner::widget(ctx, (0, 11), 1),
|
||||
]);
|
||||
|
||||
let mut tabs = TabController::new("demo_tabs".to_string(), gallery_bar_item, gallery_content);
|
||||
|
||||
let qa_bar_item = style.btn_tab.text("Conformance Checks");
|
||||
let qa_content = Widget::col(vec![
|
||||
Text::from(
|
||||
Line("Controls should be same height")
|
||||
.big_heading_styled()
|
||||
.size(18),
|
||||
)
|
||||
.into_widget(ctx),
|
||||
{
|
||||
let row_height = 10;
|
||||
let mut id = 0;
|
||||
let mut next_id = || {
|
||||
id += 1;
|
||||
format!("btn_height_check_{}", id)
|
||||
};
|
||||
Widget::row(vec![
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_outline
|
||||
.icon("system/assets/tools/layers.svg")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| style.btn_outline.text("text").build_widget(ctx, &next_id()))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_outline
|
||||
.icon_text("system/assets/tools/layers.svg", "icon+text")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_popup_icon_text("system/assets/tools/layers.svg", "icon+text")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_outline
|
||||
.popup("popup")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| widgetry::Spinner::widget(ctx, (0, 11), 1))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| widgetry::Toggle::checkbox(ctx, "checkbox", None, true))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
])
|
||||
},
|
||||
]);
|
||||
|
||||
tabs.push_tab(qa_bar_item, qa_content);
|
||||
|
||||
tabs
|
||||
}
|
||||
|
||||
fn make_controls(ctx: &mut EventCtx, tabs: &mut TabController) -> Panel {
|
||||
Panel::new(Widget::col(vec![
|
||||
Text::from_multiline(vec![
|
||||
Line("widgetry demo").big_heading_styled(),
|
||||
Line("Click and drag the background to pan, use touchpad or scroll wheel to zoom"),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
Widget::row(vec![
|
||||
ctx.style()
|
||||
.btn_outline
|
||||
@ -495,76 +586,7 @@ fn make_controls(ctx: &mut EventCtx) -> Panel {
|
||||
.build_widget(ctx, "apply"),
|
||||
])
|
||||
.margin_above(30),
|
||||
Text::from(
|
||||
Line("Controls should be same height")
|
||||
.big_heading_styled()
|
||||
.size(18),
|
||||
)
|
||||
.into_widget(ctx),
|
||||
{
|
||||
let row_height = 10;
|
||||
let mut id = 0;
|
||||
let mut next_id = || {
|
||||
id += 1;
|
||||
format!("btn_height_check_{}", id)
|
||||
};
|
||||
Widget::row(vec![
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_outline
|
||||
.icon("system/assets/tools/layers.svg")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| style.btn_outline.text("text").build_widget(ctx, &next_id()))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_outline
|
||||
.icon_text("system/assets/tools/layers.svg", "icon+text")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_popup_icon_text("system/assets/tools/layers.svg", "icon+text")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| {
|
||||
style
|
||||
.btn_outline
|
||||
.popup("popup")
|
||||
.build_widget(ctx, &next_id())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| widgetry::Spinner::widget(ctx, (0, 11), 1))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
Widget::col(
|
||||
(0..row_height)
|
||||
.map(|_| widgetry::Toggle::checkbox(ctx, "checkbox", None, true))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
])
|
||||
},
|
||||
tabs.build_widget(ctx),
|
||||
])) // end panel
|
||||
.aligned(HorizontalAlignment::Center, VerticalAlignment::Top)
|
||||
.build(ctx)
|
||||
|
Loading…
Reference in New Issue
Block a user