mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-28 12:12:00 +03:00
unify the two button types
This commit is contained in:
parent
ad20f596bf
commit
13b1e86fb7
@ -21,8 +21,8 @@ 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, JustDrawText, MenuUnderButton, ModalMenu,
|
||||
NewScroller, Scroller, Slider, SliderWithTextBox, TextButton, Warper, WarpingItemSlider,
|
||||
Wizard, WrappedWizard,
|
||||
NewScroller, Scroller, Slider, SliderWithTextBox, Warper, WarpingItemSlider, Wizard,
|
||||
WrappedWizard,
|
||||
};
|
||||
|
||||
pub enum InputResult<T: Clone> {
|
||||
|
@ -1,13 +1,16 @@
|
||||
use crate::layout::Widget;
|
||||
use crate::{
|
||||
hotkey, text, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, MultiKey, ScreenDims,
|
||||
ScreenPt, ScreenRectangle, Text,
|
||||
ScreenPt, Text,
|
||||
};
|
||||
use geom::{Circle, Distance, Polygon, Pt2D};
|
||||
|
||||
pub struct Button {
|
||||
draw_normal: Drawable,
|
||||
draw_hovered: Drawable,
|
||||
// Both of these must have the same dimensions and are oriented with their top-left corner at
|
||||
// 0, 0. Transformation happens later.
|
||||
draw_normal: DrawBoth,
|
||||
draw_hovered: DrawBoth,
|
||||
|
||||
hotkey: Option<MultiKey>,
|
||||
tooltip: Text,
|
||||
// Screenspace, top-left always at the origin. Also, probably not a box. :P
|
||||
@ -21,20 +24,18 @@ pub struct Button {
|
||||
}
|
||||
|
||||
impl Button {
|
||||
// Top-left should be at Pt2D::new(0.0, 0.0). normal and hovered must have same dimensions.
|
||||
fn new(
|
||||
normal: GeomBatch,
|
||||
hovered: GeomBatch,
|
||||
draw_normal: DrawBoth,
|
||||
draw_hovered: DrawBoth,
|
||||
hotkey: Option<MultiKey>,
|
||||
tooltip: &str,
|
||||
hitbox: Polygon,
|
||||
ctx: &EventCtx,
|
||||
) -> Button {
|
||||
let dims = normal.get_dims();
|
||||
assert_eq!(dims, hovered.get_dims());
|
||||
let dims = draw_normal.get_dims();
|
||||
assert_eq!(dims, draw_hovered.get_dims());
|
||||
Button {
|
||||
draw_normal: normal.upload(ctx),
|
||||
draw_hovered: hovered.upload(ctx),
|
||||
draw_normal,
|
||||
draw_hovered,
|
||||
hotkey,
|
||||
tooltip: if let Some(key) = hotkey {
|
||||
let mut txt = Text::from(Line(key.describe()).fg(text::HOTKEY_COLOR));
|
||||
@ -104,9 +105,9 @@ impl Button {
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
g.fork(Pt2D::new(0.0, 0.0), self.top_left, 1.0);
|
||||
if self.hovering {
|
||||
g.redraw(&self.draw_hovered);
|
||||
self.draw_hovered.draw(self.top_left, g);
|
||||
} else {
|
||||
g.redraw(&self.draw_normal);
|
||||
self.draw_normal.draw(self.top_left, g);
|
||||
}
|
||||
g.unfork();
|
||||
|
||||
@ -127,9 +128,58 @@ impl Widget for Button {
|
||||
}
|
||||
}
|
||||
|
||||
const ICON_BACKGROUND: Color = Color::grey(0.5);
|
||||
const ICON_BACKGROUND_SELECTED: Color = Color::YELLOW;
|
||||
// TODO Probably going to fold this into Drawable or do something else...
|
||||
struct DrawBoth {
|
||||
geom: Drawable,
|
||||
txt: Vec<(Text, ScreenPt)>,
|
||||
// Covers both geometry and text
|
||||
dims: ScreenDims,
|
||||
}
|
||||
|
||||
impl DrawBoth {
|
||||
fn new(ctx: &EventCtx, batch: GeomBatch, txt: Vec<(Text, ScreenPt)>) -> DrawBoth {
|
||||
let mut total_dims = batch.get_dims();
|
||||
for (t, pt) in &txt {
|
||||
let dims = ctx.canvas.text_dims(t);
|
||||
let w = dims.width + pt.x;
|
||||
let h = dims.height + pt.y;
|
||||
if w > total_dims.width {
|
||||
total_dims.width = w;
|
||||
}
|
||||
if h > total_dims.height {
|
||||
total_dims.height = h;
|
||||
}
|
||||
}
|
||||
DrawBoth {
|
||||
geom: batch.upload(ctx),
|
||||
txt,
|
||||
dims: total_dims,
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&self, top_left: ScreenPt, g: &mut GfxCtx) {
|
||||
g.redraw(&self.geom);
|
||||
for (txt, pt) in &self.txt {
|
||||
g.draw_text_at_screenspace_topleft(
|
||||
txt,
|
||||
ScreenPt::new(top_left.x + pt.x, top_left.y + pt.y),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.dims
|
||||
}
|
||||
}
|
||||
|
||||
// Stuff to construct different types of buttons
|
||||
|
||||
const CIRCULAR_ICON_BACKGROUND: Color = Color::grey(0.5);
|
||||
const CIRCULAR_ICON_BACKGROUND_SELECTED: Color = Color::YELLOW;
|
||||
const HORIZ_PADDING: f64 = 30.0;
|
||||
const VERT_PADDING: f64 = 10.0;
|
||||
|
||||
// TODO Simplify all of these APIs!
|
||||
impl Button {
|
||||
pub fn rectangle_img(filename: &str, key: Option<MultiKey>, ctx: &EventCtx) -> Button {
|
||||
let img_color = ctx.canvas.texture(filename);
|
||||
@ -145,15 +195,23 @@ impl Button {
|
||||
Distance::meters(VERT_PADDING),
|
||||
);
|
||||
|
||||
let normal = GeomBatch::from(vec![
|
||||
(Color::WHITE, bg.clone()),
|
||||
(img_color, img_rect.clone()),
|
||||
]);
|
||||
let hovered = GeomBatch::from(vec![
|
||||
(Color::ORANGE, bg.clone()),
|
||||
(img_color, img_rect.clone()),
|
||||
]);
|
||||
Button::new(normal, hovered, key, "", bg, ctx)
|
||||
let normal = DrawBoth::new(
|
||||
ctx,
|
||||
GeomBatch::from(vec![
|
||||
(Color::WHITE, bg.clone()),
|
||||
(img_color, img_rect.clone()),
|
||||
]),
|
||||
vec![],
|
||||
);
|
||||
let hovered = DrawBoth::new(
|
||||
ctx,
|
||||
GeomBatch::from(vec![
|
||||
(Color::ORANGE, bg.clone()),
|
||||
(img_color, img_rect.clone()),
|
||||
]),
|
||||
vec![],
|
||||
);
|
||||
Button::new(normal, hovered, key, "", bg)
|
||||
}
|
||||
|
||||
pub fn rectangle_img_no_bg(filename: &str, key: Option<MultiKey>, ctx: &EventCtx) -> Button {
|
||||
@ -165,9 +223,13 @@ impl Button {
|
||||
Distance::meters(dims.height),
|
||||
);
|
||||
|
||||
let normal = GeomBatch::from(vec![(color, rect.clone())]);
|
||||
let hovered = GeomBatch::from(vec![(color.with_masking(), rect.clone())]);
|
||||
Button::new(normal, hovered, key, "", rect, ctx)
|
||||
let normal = DrawBoth::new(ctx, GeomBatch::from(vec![(color, rect.clone())]), vec![]);
|
||||
let hovered = DrawBoth::new(
|
||||
ctx,
|
||||
GeomBatch::from(vec![(color.with_masking(), rect.clone())]),
|
||||
vec![],
|
||||
);
|
||||
Button::new(normal, hovered, key, "", rect)
|
||||
}
|
||||
|
||||
pub fn icon_btn_bg(
|
||||
@ -185,10 +247,16 @@ impl Button {
|
||||
normal.push(ctx.canvas.texture(icon), circle.clone());
|
||||
|
||||
let mut hovered = GeomBatch::new();
|
||||
hovered.push(ICON_BACKGROUND_SELECTED, circle.clone());
|
||||
hovered.push(CIRCULAR_ICON_BACKGROUND_SELECTED, circle.clone());
|
||||
hovered.push(ctx.canvas.texture(icon), circle.clone());
|
||||
|
||||
Button::new(normal, hovered, key, tooltip, circle, ctx)
|
||||
Button::new(
|
||||
DrawBoth::new(ctx, normal, vec![]),
|
||||
DrawBoth::new(ctx, hovered, vec![]),
|
||||
key,
|
||||
tooltip,
|
||||
circle,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn icon_btn(
|
||||
@ -198,7 +266,7 @@ impl Button {
|
||||
key: Option<MultiKey>,
|
||||
ctx: &EventCtx,
|
||||
) -> Button {
|
||||
Button::icon_btn_bg(icon, radius, tooltip, key, ICON_BACKGROUND, ctx)
|
||||
Button::icon_btn_bg(icon, radius, tooltip, key, CIRCULAR_ICON_BACKGROUND, ctx)
|
||||
}
|
||||
|
||||
pub fn show_btn(ctx: &EventCtx, tooltip: &str) -> Button {
|
||||
@ -222,116 +290,38 @@ impl Button {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn at(mut self, pt: ScreenPt) -> Button {
|
||||
self.set_pos(pt);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
const HORIZ_PADDING: f64 = 30.0;
|
||||
const VERT_PADDING: f64 = 10.0;
|
||||
|
||||
// TODO Unify with Button. Maybe Drawable should subsume MultiText (and understand screens-space
|
||||
// offsets)
|
||||
pub struct TextButton {
|
||||
bg_unselected: Drawable,
|
||||
bg_selected: Drawable,
|
||||
text: Text,
|
||||
rect: ScreenRectangle,
|
||||
hotkey: Option<MultiKey>,
|
||||
|
||||
hovering: bool,
|
||||
clicked: bool,
|
||||
}
|
||||
|
||||
impl TextButton {
|
||||
pub fn new(
|
||||
text: Text,
|
||||
pub fn text(
|
||||
mut text: Text,
|
||||
unselected_bg_color: Color,
|
||||
selected_bg_color: Color,
|
||||
hotkey: Option<MultiKey>,
|
||||
ctx: &EventCtx,
|
||||
) -> TextButton {
|
||||
) -> Button {
|
||||
text = text.no_bg();
|
||||
let dims = ctx.canvas.text_dims(&text);
|
||||
let geom = Polygon::rounded_rectangle(
|
||||
Distance::meters(dims.width + 2.0 * HORIZ_PADDING),
|
||||
Distance::meters(dims.height + 2.0 * VERT_PADDING),
|
||||
Distance::meters(VERT_PADDING),
|
||||
);
|
||||
let draw_text = vec![(text, ScreenPt::new(HORIZ_PADDING, VERT_PADDING))];
|
||||
|
||||
TextButton {
|
||||
bg_unselected: GeomBatch::from(vec![(unselected_bg_color, geom.clone())]).upload(ctx),
|
||||
bg_selected: GeomBatch::from(vec![(selected_bg_color, geom)]).upload(ctx),
|
||||
text: text.no_bg(),
|
||||
rect: ScreenRectangle::top_left(
|
||||
ScreenPt::new(0.0, 0.0),
|
||||
ScreenDims::new(
|
||||
dims.width + 2.0 * HORIZ_PADDING,
|
||||
dims.height + 2.0 * VERT_PADDING,
|
||||
),
|
||||
),
|
||||
hotkey,
|
||||
|
||||
hovering: false,
|
||||
clicked: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(&mut self, ctx: &mut EventCtx) {
|
||||
if self.clicked {
|
||||
panic!("Caller didn't consume button click");
|
||||
}
|
||||
|
||||
if ctx.redo_mouseover() {
|
||||
self.hovering = self.rect.contains(ctx.canvas.get_cursor_in_screen_space());
|
||||
}
|
||||
if self.hovering && ctx.input.left_mouse_button_pressed() {
|
||||
self.clicked = true;
|
||||
}
|
||||
|
||||
if let Some(hotkey) = self.hotkey {
|
||||
if ctx.input.new_was_pressed(hotkey) {
|
||||
self.clicked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clicked(&mut self) -> bool {
|
||||
if self.clicked {
|
||||
self.clicked = false;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw(&self, g: &mut GfxCtx) {
|
||||
g.fork(
|
||||
Pt2D::new(0.0, 0.0),
|
||||
ScreenPt::new(self.rect.x1, self.rect.y1),
|
||||
1.0,
|
||||
let normal = DrawBoth::new(
|
||||
ctx,
|
||||
GeomBatch::from(vec![(unselected_bg_color, geom.clone())]),
|
||||
draw_text.clone(),
|
||||
);
|
||||
if self.hovering {
|
||||
g.redraw(&self.bg_selected);
|
||||
} else {
|
||||
g.redraw(&self.bg_unselected);
|
||||
}
|
||||
g.unfork();
|
||||
|
||||
g.canvas.mark_covered_area(self.rect.clone());
|
||||
g.draw_text_at_screenspace_topleft(
|
||||
&self.text,
|
||||
ScreenPt::new(self.rect.x1 + HORIZ_PADDING, self.rect.y1 + VERT_PADDING),
|
||||
let hovered = DrawBoth::new(
|
||||
ctx,
|
||||
GeomBatch::from(vec![(selected_bg_color, geom.clone())]),
|
||||
draw_text,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for TextButton {
|
||||
fn get_dims(&self) -> ScreenDims {
|
||||
self.rect.dims()
|
||||
}
|
||||
|
||||
fn set_pos(&mut self, top_left: ScreenPt) {
|
||||
self.rect = ScreenRectangle::top_left(top_left, self.rect.dims());
|
||||
|
||||
Button::new(normal, hovered, hotkey, "", geom)
|
||||
}
|
||||
|
||||
pub fn at(mut self, pt: ScreenPt) -> Button {
|
||||
self.set_pos(pt);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod warper;
|
||||
mod wizard;
|
||||
|
||||
pub use self::autocomplete::Autocomplete;
|
||||
pub use self::button::{Button, TextButton};
|
||||
pub use self::button::Button;
|
||||
pub(crate) use self::context_menu::ContextMenu;
|
||||
pub use self::menu_under_button::MenuUnderButton;
|
||||
pub use self::modal_menu::ModalMenu;
|
||||
|
@ -2,7 +2,6 @@ use crate::game::{State, Transition};
|
||||
use crate::ui::UI;
|
||||
use ezgui::{
|
||||
layout, Button, Color, EventCtx, GfxCtx, JustDraw, JustDrawText, Line, MultiKey, Text,
|
||||
TextButton,
|
||||
};
|
||||
|
||||
type Callback = Box<dyn Fn(&mut EventCtx, &mut UI) -> Option<Transition>>;
|
||||
@ -19,7 +18,7 @@ impl<'a> ManagedGUIStateBuilder<'a> {
|
||||
|
||||
pub fn img_button(&mut self, filename: &str, hotkey: Option<MultiKey>, onclick: Callback) {
|
||||
let btn = Button::rectangle_img(filename, hotkey, self.ctx);
|
||||
self.state.img_buttons.push((btn, onclick));
|
||||
self.state.buttons.push((btn, onclick));
|
||||
}
|
||||
|
||||
pub fn img_button_no_bg(
|
||||
@ -29,7 +28,7 @@ impl<'a> ManagedGUIStateBuilder<'a> {
|
||||
onclick: Callback,
|
||||
) {
|
||||
let btn = Button::rectangle_img_no_bg(filename, hotkey, self.ctx);
|
||||
self.state.img_buttons.push((btn, onclick));
|
||||
self.state.buttons.push((btn, onclick));
|
||||
}
|
||||
|
||||
pub fn text_button(&mut self, label: &str, hotkey: Option<MultiKey>, onclick: Callback) {
|
||||
@ -38,7 +37,7 @@ impl<'a> ManagedGUIStateBuilder<'a> {
|
||||
|
||||
pub fn detailed_text_button(&mut self, txt: Text, hotkey: Option<MultiKey>, onclick: Callback) {
|
||||
// TODO Default style. Lots of variations.
|
||||
let btn = TextButton::new(txt, Color::WHITE, Color::ORANGE, hotkey, self.ctx);
|
||||
let btn = Button::text(txt, Color::WHITE, Color::ORANGE, hotkey, self.ctx);
|
||||
self.state.buttons.push((btn, onclick));
|
||||
}
|
||||
|
||||
@ -49,9 +48,7 @@ impl<'a> ManagedGUIStateBuilder<'a> {
|
||||
|
||||
pub struct ManagedGUIState {
|
||||
draw_text: Vec<JustDrawText>,
|
||||
// TODO Unify these
|
||||
buttons: Vec<(TextButton, Callback)>,
|
||||
img_buttons: Vec<(Button, Callback)>,
|
||||
buttons: Vec<(Button, Callback)>,
|
||||
}
|
||||
|
||||
impl ManagedGUIState {
|
||||
@ -61,7 +58,6 @@ impl ManagedGUIState {
|
||||
state: ManagedGUIState {
|
||||
draw_text: Vec::new(),
|
||||
buttons: Vec::new(),
|
||||
img_buttons: Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -80,11 +76,6 @@ impl State for ManagedGUIState {
|
||||
.iter_mut()
|
||||
.map(|(btn, _)| btn as &mut dyn layout::Widget),
|
||||
)
|
||||
.chain(
|
||||
self.img_buttons
|
||||
.iter_mut()
|
||||
.map(|(btn, _)| btn as &mut dyn layout::Widget),
|
||||
)
|
||||
.collect(),
|
||||
);
|
||||
for (btn, onclick) in self.buttons.iter_mut() {
|
||||
@ -95,14 +86,6 @@ impl State for ManagedGUIState {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (btn, onclick) in self.img_buttons.iter_mut() {
|
||||
btn.event(ctx);
|
||||
if btn.clicked() {
|
||||
if let Some(t) = (onclick)(ctx, ui) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
@ -119,8 +102,5 @@ impl State for ManagedGUIState {
|
||||
for (btn, _) in &self.buttons {
|
||||
btn.draw(g);
|
||||
}
|
||||
for (btn, _) in &self.img_buttons {
|
||||
btn.draw(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use crate::tutorial::TutorialMode;
|
||||
use crate::ui::UI;
|
||||
use abstutil::elapsed_seconds;
|
||||
use ezgui::{
|
||||
hotkey, layout, Color, EventCtx, EventLoopMode, GfxCtx, JustDraw, Key, Line, Text, TextButton,
|
||||
hotkey, layout, Button, Color, EventCtx, EventLoopMode, GfxCtx, JustDraw, Key, Line, Text,
|
||||
};
|
||||
use geom::{Duration, Line, Pt2D, Speed};
|
||||
use map_model::Map;
|
||||
@ -18,7 +18,7 @@ use std::time::Instant;
|
||||
|
||||
pub struct TitleScreen {
|
||||
logo: JustDraw,
|
||||
play_btn: TextButton,
|
||||
play_btn: Button,
|
||||
screensaver: Screensaver,
|
||||
rng: XorShiftRng,
|
||||
}
|
||||
@ -30,7 +30,7 @@ impl TitleScreen {
|
||||
logo: JustDraw::image("assets/pregame/logo.png", ctx),
|
||||
// TODO that nicer font
|
||||
// TODO Any key
|
||||
play_btn: TextButton::new(
|
||||
play_btn: Button::text(
|
||||
Text::from(Line("PLAY")),
|
||||
Color::BLUE,
|
||||
Color::ORANGE,
|
||||
|
Loading…
Reference in New Issue
Block a user