unify the two button types

This commit is contained in:
Dustin Carlino 2019-11-24 07:21:21 -08:00
parent ad20f596bf
commit 13b1e86fb7
5 changed files with 128 additions and 158 deletions

View File

@ -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> {

View File

@ -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
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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,