2019-01-07 07:54:37 +00:00
|
|
|
use std::{
|
|
|
|
hash::Hash,
|
|
|
|
sync::{Arc, Mutex},
|
|
|
|
};
|
2018-12-27 18:08:43 +00:00
|
|
|
|
2020-04-17 13:29:48 +00:00
|
|
|
use crate::{widgets::*, *};
|
2018-12-26 09:46:23 +00:00
|
|
|
|
2018-12-26 22:08:50 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
2018-12-26 09:46:23 +00:00
|
|
|
|
2019-01-16 15:28:43 +00:00
|
|
|
// TODO: rename GuiResponse
|
2019-01-07 07:54:37 +00:00
|
|
|
pub struct GuiResponse {
|
2018-12-28 22:53:15 +00:00
|
|
|
/// 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,
|
|
|
|
|
2019-01-14 13:26:02 +00:00
|
|
|
/// The region of the screen we are talking about
|
|
|
|
pub rect: Rect,
|
|
|
|
|
2019-01-06 15:34:01 +00:00
|
|
|
/// Used for showing a popup (if any)
|
2020-04-17 13:29:48 +00:00
|
|
|
pub data: Arc<Data>,
|
2018-12-28 22:53:15 +00:00
|
|
|
}
|
|
|
|
|
2019-01-07 07:54:37 +00:00
|
|
|
impl GuiResponse {
|
2018-12-28 22:53:15 +00:00
|
|
|
/// Show some stuff if the item was hovered
|
2019-01-06 15:34:01 +00:00
|
|
|
pub fn tooltip<F>(&mut self, add_contents: F) -> &mut Self
|
2018-12-28 22:53:15 +00:00
|
|
|
where
|
2019-01-06 15:34:01 +00:00
|
|
|
F: FnOnce(&mut Region),
|
2018-12-28 22:53:15 +00:00
|
|
|
{
|
|
|
|
if self.hovered {
|
2019-02-10 14:30:48 +00:00
|
|
|
if let Some(mouse_pos) = self.data.input().mouse_pos {
|
|
|
|
let window_pos = mouse_pos + vec2(16.0, 16.0);
|
|
|
|
show_popup(&self.data, window_pos, add_contents);
|
|
|
|
}
|
2018-12-28 22:53:15 +00:00
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Show this text if the item was hovered
|
2019-01-06 15:34:01 +00:00
|
|
|
pub fn tooltip_text<S: Into<String>>(&mut self, text: S) -> &mut Self {
|
2018-12-28 22:53:15 +00:00
|
|
|
self.tooltip(|popup| {
|
2019-01-21 07:48:32 +00:00
|
|
|
popup.add(Label::new(text));
|
2018-12-28 22:53:15 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2018-12-28 09:39:08 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
2019-01-06 15:34:01 +00:00
|
|
|
pub enum Direction {
|
2018-12-28 09:39:08 +00:00
|
|
|
Horizontal,
|
|
|
|
Vertical,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Direction {
|
|
|
|
fn default() -> Direction {
|
|
|
|
Direction::Vertical
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-14 13:54:06 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
|
|
pub enum Align {
|
|
|
|
/// Left/Top
|
|
|
|
Min,
|
|
|
|
|
|
|
|
/// Note: requires a bounded/known available_width.
|
|
|
|
Center,
|
|
|
|
|
|
|
|
/// Right/Bottom
|
|
|
|
/// Note: requires a bounded/known available_width.
|
|
|
|
Max,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Align {
|
|
|
|
fn default() -> Align {
|
|
|
|
Align::Min
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-28 09:39:08 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-04-17 12:26:36 +00:00
|
|
|
// TODO: newtype
|
2019-01-10 09:55:38 +00:00
|
|
|
pub type Id = u64;
|
|
|
|
|
|
|
|
pub fn make_id<H: Hash>(source: &H) -> Id {
|
|
|
|
use std::hash::Hasher;
|
|
|
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
|
|
source.hash(&mut hasher);
|
|
|
|
hasher.finish()
|
|
|
|
}
|
2018-12-26 22:08:50 +00:00
|
|
|
|
2019-01-07 07:54:37 +00:00
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
|
2020-04-17 12:26:36 +00:00
|
|
|
// TODO: give a better name. Context?
|
2019-03-11 14:39:54 +00:00
|
|
|
/// Contains the input, style and output of all GUI commands.
|
2019-01-06 15:34:01 +00:00
|
|
|
pub struct Data {
|
2019-03-11 14:39:54 +00:00
|
|
|
/// The default style for new regions
|
|
|
|
pub(crate) style: Mutex<Style>,
|
2019-01-12 23:55:56 +00:00
|
|
|
pub(crate) fonts: Arc<Fonts>,
|
2019-01-06 15:34:01 +00:00
|
|
|
pub(crate) input: GuiInput,
|
2019-01-07 07:54:37 +00:00
|
|
|
pub(crate) memory: Mutex<Memory>,
|
|
|
|
pub(crate) graphics: Mutex<GraphicLayers>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Clone for Data {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Data {
|
2019-03-11 14:39:54 +00:00
|
|
|
style: Mutex::new(self.style()),
|
2019-01-12 23:55:56 +00:00
|
|
|
fonts: self.fonts.clone(),
|
2019-01-16 15:28:43 +00:00
|
|
|
input: self.input,
|
2019-01-07 07:54:37 +00:00
|
|
|
memory: Mutex::new(self.memory.lock().unwrap().clone()),
|
|
|
|
graphics: Mutex::new(self.graphics.lock().unwrap().clone()),
|
|
|
|
}
|
|
|
|
}
|
2018-12-26 14:28:38 +00:00
|
|
|
}
|
2018-12-26 09:46:23 +00:00
|
|
|
|
2019-01-06 15:34:01 +00:00
|
|
|
impl Data {
|
2019-01-19 16:10:28 +00:00
|
|
|
pub fn new(pixels_per_point: f32) -> Data {
|
2019-01-06 15:34:01 +00:00
|
|
|
Data {
|
2019-03-11 14:39:54 +00:00
|
|
|
style: Default::default(),
|
2019-01-19 16:10:28 +00:00
|
|
|
fonts: Arc::new(Fonts::new(pixels_per_point)),
|
2019-01-05 14:28:07 +00:00
|
|
|
input: Default::default(),
|
|
|
|
memory: Default::default(),
|
|
|
|
graphics: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-26 09:46:23 +00:00
|
|
|
pub fn input(&self) -> &GuiInput {
|
|
|
|
&self.input
|
|
|
|
}
|
|
|
|
|
2019-03-11 14:39:54 +00:00
|
|
|
pub fn style(&self) -> Style {
|
|
|
|
*self.style.lock().unwrap()
|
2018-12-28 22:29:24 +00:00
|
|
|
}
|
|
|
|
|
2019-03-11 14:39:54 +00:00
|
|
|
pub fn set_style(&self, style: Style) {
|
|
|
|
*self.style.lock().unwrap() = style;
|
2018-12-26 09:46:23 +00:00
|
|
|
}
|
|
|
|
|
2018-12-28 09:39:08 +00:00
|
|
|
// TODO: move
|
2018-12-28 22:29:24 +00:00
|
|
|
pub fn new_frame(&mut self, gui_input: GuiInput) {
|
2018-12-28 09:39:08 +00:00
|
|
|
self.input = gui_input;
|
2019-02-10 14:30:48 +00:00
|
|
|
if !gui_input.mouse_down || gui_input.mouse_pos.is_none() {
|
2019-01-07 07:54:37 +00:00
|
|
|
self.memory.lock().unwrap().active_id = None;
|
2018-12-28 09:39:08 +00:00
|
|
|
}
|
|
|
|
}
|
2019-02-10 21:27:41 +00:00
|
|
|
|
|
|
|
/// Is the user interacting with anything?
|
|
|
|
pub fn any_active(&self) -> bool {
|
|
|
|
self.memory.lock().unwrap().active_id.is_some()
|
|
|
|
}
|
2020-04-17 12:26:36 +00:00
|
|
|
|
|
|
|
pub fn interact(&self, layer: Layer, rect: Rect, interaction_id: Option<Id>) -> InteractInfo {
|
|
|
|
let mut memory = self.memory.lock().unwrap();
|
|
|
|
|
|
|
|
let hovered = if let Some(mouse_pos) = self.input.mouse_pos {
|
|
|
|
if rect.contains(mouse_pos) {
|
|
|
|
let is_something_else_active =
|
|
|
|
memory.active_id.is_some() && memory.active_id != interaction_id;
|
|
|
|
|
|
|
|
!is_something_else_active && layer == memory.layer_at(mouse_pos)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
let active = if interaction_id.is_some() {
|
|
|
|
if hovered && self.input.mouse_clicked {
|
|
|
|
memory.active_id = interaction_id;
|
|
|
|
}
|
|
|
|
memory.active_id == interaction_id
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
|
|
|
|
let clicked = hovered && self.input.mouse_released;
|
|
|
|
|
|
|
|
InteractInfo {
|
|
|
|
rect,
|
|
|
|
hovered,
|
|
|
|
clicked,
|
|
|
|
active,
|
|
|
|
}
|
|
|
|
}
|
2019-01-07 07:54:37 +00:00
|
|
|
}
|
2018-12-28 09:39:08 +00:00
|
|
|
|
2020-04-12 10:07:51 +00:00
|
|
|
impl Data {
|
|
|
|
pub fn style_ui(&self, region: &mut Region) {
|
|
|
|
let mut style = self.style();
|
|
|
|
style.ui(region);
|
|
|
|
self.set_style(style);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-07 07:54:37 +00:00
|
|
|
/// Show a pop-over window
|
|
|
|
pub fn show_popup<F>(data: &Arc<Data>, window_pos: Vec2, add_contents: F)
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut Region),
|
|
|
|
{
|
2020-04-17 12:26:36 +00:00
|
|
|
let layer = Layer::Popup;
|
|
|
|
let where_to_put_background = data.graphics.lock().unwrap().layer(layer).len();
|
2019-01-07 07:54:37 +00:00
|
|
|
|
2019-03-11 14:39:54 +00:00
|
|
|
let style = data.style();
|
|
|
|
let window_padding = style.window_padding;
|
2019-01-07 07:54:37 +00:00
|
|
|
|
2020-04-17 12:26:36 +00:00
|
|
|
let mut contents_region = Region {
|
2019-01-07 07:54:37 +00:00
|
|
|
data: data.clone(),
|
2020-04-17 12:26:36 +00:00
|
|
|
layer: Layer::Popup,
|
2019-03-11 14:39:54 +00:00
|
|
|
style,
|
2019-01-07 07:54:37 +00:00
|
|
|
id: Default::default(),
|
|
|
|
dir: Direction::Vertical,
|
2019-01-14 13:54:06 +00:00
|
|
|
align: Align::Min,
|
2019-01-07 07:54:37 +00:00
|
|
|
cursor: window_pos + window_padding,
|
|
|
|
bounding_size: vec2(0.0, 0.0),
|
2019-01-19 16:09:00 +00:00
|
|
|
available_space: vec2(data.input.screen_size.x.min(350.0), std::f32::INFINITY), // TODO: popup/tooltip width
|
2019-01-07 07:54:37 +00:00
|
|
|
};
|
|
|
|
|
2020-04-17 12:26:36 +00:00
|
|
|
add_contents(&mut contents_region);
|
|
|
|
|
|
|
|
// Now insert popup background:
|
2019-01-07 07:54:37 +00:00
|
|
|
|
|
|
|
// TODO: handle the last item_spacing in a nicer way
|
2020-04-17 12:26:36 +00:00
|
|
|
let inner_size = contents_region.bounding_size - style.item_spacing;
|
2019-01-07 07:54:37 +00:00
|
|
|
let outer_size = inner_size + 2.0 * window_padding;
|
|
|
|
|
|
|
|
let rect = Rect::from_min_size(window_pos, outer_size);
|
|
|
|
|
|
|
|
let mut graphics = data.graphics.lock().unwrap();
|
2020-04-17 12:26:36 +00:00
|
|
|
let graphics = graphics.layer(layer);
|
|
|
|
graphics.insert(
|
|
|
|
where_to_put_background,
|
|
|
|
PaintCmd::Rect {
|
|
|
|
corner_radius: 5.0,
|
|
|
|
fill_color: Some(style.background_fill_color()),
|
|
|
|
outline: Some(Outline {
|
|
|
|
color: color::WHITE,
|
|
|
|
width: 1.0,
|
|
|
|
}),
|
|
|
|
rect,
|
|
|
|
},
|
|
|
|
);
|
2019-01-06 15:34:01 +00:00
|
|
|
}
|