Browse Source

Ergonomic tooltips

pull/1/head
Emil Ernerfeldt 6 years ago
parent
commit
d05c03d1eb
  1. 15
      src/app.rs
  2. 115
      src/layout.rs
  3. 2
      src/style.rs
  4. 1
      src/types.rs

15
src/app.rs

@ -37,12 +37,11 @@ impl GuiSettings for App {
gui.input().screen_size.y, gui.input().screen_size.y,
)); ));
// TODO: add tooltip text with: gui.button("click me").tooltip_text("tooltip") gui.label("Hover me").tooltip_text(
if gui.checkbox("checkbox", &mut self.checked).hovered { "This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
gui.tooltip_text(
"This is a multiline tooltip that explains the checkbox you are hovering.\nThis is the second line.\nThis is the third.",
); );
}
gui.checkbox("checkbox", &mut self.checked);
gui.horizontal(|gui| { gui.horizontal(|gui| {
if gui.radio("First", self.selected_alternative == 0).clicked { if gui.radio("First", self.selected_alternative == 0).clicked {
@ -56,7 +55,11 @@ impl GuiSettings for App {
} }
}); });
if gui.button("Click me").clicked { if gui
.button("Click me")
.tooltip_text("This will just increase a counter.")
.clicked
{
self.count += 1; self.count += 1;
} }

115
src/layout.rs

@ -46,6 +46,43 @@ impl Default for LayoutOptions {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// TODO: rename
pub struct GuiResponse<'a> {
/// The mouse is hovering above this
pub hovered: bool,
/// The mouse went got pressed on this thing this frame
pub clicked: bool,
/// The mouse is interacting with this thing (e.g. dragging it)
pub active: bool,
layout: &'a mut Layout,
}
impl<'a> GuiResponse<'a> {
/// Show some stuff if the item was hovered
pub fn tooltip<F>(self, add_contents: F) -> Self
where
F: FnOnce(&mut Layout),
{
if self.hovered {
let window_pos = self.layout.input.mouse_pos + vec2(16.0, 16.0);
self.layout.show_popup(window_pos, add_contents);
}
self
}
/// Show this text if the item was hovered
pub fn tooltip_text<S: Into<String>>(self, text: S) -> Self {
self.tooltip(|popup| {
popup.label(text);
})
}
}
// ----------------------------------------------------------------------------
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct Memory { struct Memory {
/// The widget being interacted with (e.g. dragged, in case of a slider). /// The widget being interacted with (e.g. dragged, in case of a slider).
@ -153,31 +190,31 @@ impl Layout {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
pub fn button<S: Into<String>>(&mut self, text: S) -> InteractInfo { pub fn button<S: Into<String>>(&mut self, text: S) -> GuiResponse {
let text: String = text.into(); let text: String = text.into();
let id = self.get_id(&text); let id = self.get_id(&text);
let (text, text_size) = self.layout_text(&text); let (text, text_size) = self.layout_text(&text);
let text_cursor = self.layouter.cursor + self.options.button_padding; let text_cursor = self.layouter.cursor + self.options.button_padding;
let (rect, interact) = let (rect, interact) =
self.reserve_interactive_space(id, text_size + 2.0 * self.options.button_padding); self.reserve_space(text_size + 2.0 * self.options.button_padding, Some(id));
self.graphics.push(GuiCmd::Button { interact, rect }); self.graphics.push(GuiCmd::Button { interact, rect });
self.add_text(text_cursor, text); self.add_text(text_cursor, text);
interact self.response(interact)
} }
pub fn checkbox<S: Into<String>>(&mut self, text: S, checked: &mut bool) -> InteractInfo { pub fn checkbox<S: Into<String>>(&mut self, text: S, checked: &mut bool) -> GuiResponse {
let text: String = text.into(); let text: String = text.into();
let id = self.get_id(&text); let id = self.get_id(&text);
let (text, text_size) = self.layout_text(&text); let (text, text_size) = self.layout_text(&text);
let text_cursor = self.layouter.cursor let text_cursor = self.layouter.cursor
+ self.options.button_padding + self.options.button_padding
+ vec2(self.options.start_icon_width, 0.0); + vec2(self.options.start_icon_width, 0.0);
let (rect, interact) = self.reserve_interactive_space( let (rect, interact) = self.reserve_space(
id,
self.options.button_padding self.options.button_padding
+ vec2(self.options.start_icon_width, 0.0) + vec2(self.options.start_icon_width, 0.0)
+ text_size + text_size
+ self.options.button_padding, + self.options.button_padding,
Some(id),
); );
if interact.clicked { if interact.clicked {
*checked = !*checked; *checked = !*checked;
@ -188,30 +225,31 @@ impl Layout {
rect, rect,
}); });
self.add_text(text_cursor, text); self.add_text(text_cursor, text);
interact self.response(interact)
} }
pub fn label<S: Into<String>>(&mut self, text: S) { pub fn label<S: Into<String>>(&mut self, text: S) -> GuiResponse {
let text: String = text.into(); let text: String = text.into();
let (text, text_size) = self.layout_text(&text); let (text, text_size) = self.layout_text(&text);
self.add_text(self.layouter.cursor, text); self.add_text(self.layouter.cursor, text);
self.reserve_space_default_spacing(text_size); let (_, interact) = self.reserve_space(text_size, None);
self.response(interact)
} }
/// A radio button /// A radio button
pub fn radio<S: Into<String>>(&mut self, text: S, checked: bool) -> InteractInfo { pub fn radio<S: Into<String>>(&mut self, text: S, checked: bool) -> GuiResponse {
let text: String = text.into(); let text: String = text.into();
let id = self.get_id(&text); let id = self.get_id(&text);
let (text, text_size) = self.layout_text(&text); let (text, text_size) = self.layout_text(&text);
let text_cursor = self.layouter.cursor let text_cursor = self.layouter.cursor
+ self.options.button_padding + self.options.button_padding
+ vec2(self.options.start_icon_width, 0.0); + vec2(self.options.start_icon_width, 0.0);
let (rect, interact) = self.reserve_interactive_space( let (rect, interact) = self.reserve_space(
id,
self.options.button_padding self.options.button_padding
+ vec2(self.options.start_icon_width, 0.0) + vec2(self.options.start_icon_width, 0.0)
+ text_size + text_size
+ self.options.button_padding, + self.options.button_padding,
Some(id),
); );
self.graphics.push(GuiCmd::RadioButton { self.graphics.push(GuiCmd::RadioButton {
checked, checked,
@ -219,7 +257,7 @@ impl Layout {
rect, rect,
}); });
self.add_text(text_cursor, text); self.add_text(text_cursor, text);
interact self.response(interact)
} }
pub fn slider_f32<S: Into<String>>( pub fn slider_f32<S: Into<String>>(
@ -228,19 +266,19 @@ impl Layout {
value: &mut f32, value: &mut f32,
min: f32, min: f32,
max: f32, max: f32,
) -> InteractInfo { ) -> GuiResponse {
debug_assert!(min <= max); debug_assert!(min <= max);
let text: String = text.into(); let text: String = text.into();
let id = self.get_id(&text); let id = self.get_id(&text);
let (text, text_size) = self.layout_text(&format!("{}: {:.3}", text, value)); let (text, text_size) = self.layout_text(&format!("{}: {:.3}", text, value));
self.add_text(self.layouter.cursor, text); self.add_text(self.layouter.cursor, text);
self.layouter.reserve_space(text_size); self.layouter.reserve_space(text_size);
let (slider_rect, interact) = self.reserve_interactive_space( let (slider_rect, interact) = self.reserve_space(
id,
Vec2 { Vec2 {
x: self.options.width, x: self.options.width,
y: self.options.char_size.y, y: self.options.char_size.y,
}, },
Some(id),
); );
if interact.active { if interact.active {
@ -261,13 +299,13 @@ impl Layout {
value: *value, value: *value,
}); });
interact self.response(interact)
} }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Areas: // Areas:
pub fn foldable<S, F>(&mut self, text: S, add_contents: F) -> InteractInfo pub fn foldable<S, F>(&mut self, text: S, add_contents: F) -> GuiResponse
where where
S: Into<String>, S: Into<String>,
F: FnOnce(&mut Layout), F: FnOnce(&mut Layout),
@ -280,12 +318,12 @@ impl Layout {
let id = self.get_id(&text); let id = self.get_id(&text);
let (text, text_size) = self.layout_text(&text); let (text, text_size) = self.layout_text(&text);
let text_cursor = self.layouter.cursor + self.options.button_padding; let text_cursor = self.layouter.cursor + self.options.button_padding;
let (rect, interact) = self.reserve_interactive_space( let (rect, interact) = self.reserve_space(
id,
vec2( vec2(
self.options.width, self.options.width,
text_size.y + 2.0 * self.options.button_padding.y, text_size.y + 2.0 * self.options.button_padding.y,
), ),
Some(id),
); );
if interact.clicked { if interact.clicked {
@ -314,7 +352,7 @@ impl Layout {
self.id = old_id; self.id = old_id;
} }
interact self.response(interact)
} }
/// Start a region with horizontal layout /// Start a region with horizontal layout
@ -341,10 +379,11 @@ impl Layout {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/// Show some text in a window under mouse position. /// Show a pop-over window
pub fn tooltip_text<S: Into<String>>(&mut self, text: S) { pub fn show_popup<F>(&mut self, window_pos: Vec2, add_contents: F)
let window_pos = self.input.mouse_pos + vec2(16.0, 16.0); where
F: FnOnce(&mut Layout),
{
// TODO: less copying // TODO: less copying
let mut popup_layout = Layout { let mut popup_layout = Layout {
options: self.options, options: self.options,
@ -357,7 +396,7 @@ impl Layout {
}; };
popup_layout.layouter.cursor = window_pos + self.options.window_padding; popup_layout.layouter.cursor = window_pos + self.options.window_padding;
popup_layout.label(text); add_contents(&mut popup_layout);
// TODO: handle the last item_spacing in a nicer way // TODO: handle the last item_spacing in a nicer way
let inner_size = popup_layout.layouter.size - self.options.item_spacing; let inner_size = popup_layout.layouter.size - self.options.item_spacing;
@ -371,24 +410,23 @@ impl Layout {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
fn reserve_space_default_spacing(&mut self, size: Vec2) -> Rect { fn reserve_space(&mut self, size: Vec2, interaction_id: Option<Id>) -> (Rect, InteractInfo) {
let rect = Rect { let rect = Rect {
pos: self.layouter.cursor, pos: self.layouter.cursor,
size, size,
}; };
self.layouter self.layouter
.reserve_space(size + self.options.item_spacing); .reserve_space(size + self.options.item_spacing);
rect
}
fn reserve_interactive_space(&mut self, id: Id, size: Vec2) -> (Rect, InteractInfo) {
let rect = self.reserve_space_default_spacing(size);
let hovered = rect.contains(self.input.mouse_pos); let hovered = rect.contains(self.input.mouse_pos);
let clicked = hovered && self.input.mouse_clicked; let clicked = hovered && self.input.mouse_clicked;
let active = if interaction_id.is_some() {
if clicked { if clicked {
self.memory.active_id = Some(id); self.memory.active_id = interaction_id;
} }
let active = self.memory.active_id == Some(id); self.memory.active_id == interaction_id
} else {
false
};
let interact = InteractInfo { let interact = InteractInfo {
hovered, hovered,
@ -436,4 +474,13 @@ impl Layout {
}); });
} }
} }
fn response(&mut self, interact: InteractInfo) -> GuiResponse {
GuiResponse {
hovered: interact.hovered,
clicked: interact.clicked,
active: interact.active,
layout: self,
}
}
} }

2
src/style.rs

@ -30,7 +30,7 @@ impl Default for Style {
impl Style { impl Style {
/// e.g. the background of the slider /// e.g. the background of the slider
fn background_fill_color(&self) -> Color { fn background_fill_color(&self) -> Color {
srgba(34, 34, 34, 255) srgba(34, 34, 34, 200)
} }
fn text_color(&self) -> Color { fn text_color(&self) -> Color {

1
src/types.rs

@ -65,6 +65,7 @@ pub fn srgba(r: u8, g: u8, b: u8, a: u8) -> Color {
#[derive(Clone, Copy, Debug, Default, Serialize)] #[derive(Clone, Copy, Debug, Default, Serialize)]
pub struct InteractInfo { pub struct InteractInfo {
/// The mouse is hovering above this
pub hovered: bool, pub hovered: bool,
/// The mouse went got pressed on this thing this frame /// The mouse went got pressed on this thing this frame

Loading…
Cancel
Save