2020-04-19 09:13:24 +00:00
|
|
|
use std::{collections::HashMap, sync::Arc};
|
2020-04-17 21:30:01 +00:00
|
|
|
|
|
|
|
use parking_lot::Mutex;
|
2020-04-17 13:33:52 +00:00
|
|
|
|
2020-04-19 09:13:24 +00:00
|
|
|
use crate::{layout::align_rect, *};
|
2020-04-17 13:33:52 +00:00
|
|
|
|
|
|
|
/// Contains the input, style and output of all GUI commands.
|
|
|
|
pub struct Context {
|
|
|
|
/// The default style for new regions
|
|
|
|
pub(crate) style: Mutex<Style>,
|
|
|
|
pub(crate) fonts: Arc<Fonts>,
|
2020-04-28 21:05:22 +00:00
|
|
|
/// Raw input from last frame. Use `input()` instead.
|
2020-05-03 11:28:47 +00:00
|
|
|
last_raw_input: RawInput,
|
2020-04-17 13:33:52 +00:00
|
|
|
pub(crate) input: GuiInput,
|
2020-05-03 11:28:47 +00:00
|
|
|
mouse_tracker: MovementTracker<Pos2>,
|
2020-05-02 09:37:12 +00:00
|
|
|
memory: Mutex<Memory>,
|
2020-04-17 13:33:52 +00:00
|
|
|
pub(crate) graphics: Mutex<GraphicLayers>,
|
2020-04-19 09:13:24 +00:00
|
|
|
|
2020-05-02 09:37:12 +00:00
|
|
|
output: Mutex<Output>,
|
2020-04-20 08:01:13 +00:00
|
|
|
|
2020-04-19 09:13:24 +00:00
|
|
|
/// Used to debug name clashes of e.g. windows
|
|
|
|
used_ids: Mutex<HashMap<Id, Pos2>>,
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
|
2020-04-19 09:13:24 +00:00
|
|
|
// TODO: remove this impl.
|
2020-04-17 13:33:52 +00:00
|
|
|
impl Clone for Context {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Context {
|
|
|
|
style: Mutex::new(self.style()),
|
|
|
|
fonts: self.fonts.clone(),
|
2020-04-28 21:05:22 +00:00
|
|
|
last_raw_input: self.last_raw_input.clone(),
|
|
|
|
input: self.input.clone(),
|
2020-05-03 11:28:47 +00:00
|
|
|
mouse_tracker: self.mouse_tracker.clone(),
|
2020-04-17 21:30:01 +00:00
|
|
|
memory: Mutex::new(self.memory.lock().clone()),
|
|
|
|
graphics: Mutex::new(self.graphics.lock().clone()),
|
2020-04-23 17:15:17 +00:00
|
|
|
output: Mutex::new(self.output.lock().clone()),
|
2020-04-19 09:13:24 +00:00
|
|
|
used_ids: Mutex::new(self.used_ids.lock().clone()),
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
|
|
|
pub fn new(pixels_per_point: f32) -> Context {
|
|
|
|
Context {
|
|
|
|
style: Default::default(),
|
|
|
|
fonts: Arc::new(Fonts::new(pixels_per_point)),
|
2020-04-28 21:05:22 +00:00
|
|
|
last_raw_input: Default::default(),
|
2020-04-17 13:33:52 +00:00
|
|
|
input: Default::default(),
|
2020-05-03 11:28:47 +00:00
|
|
|
mouse_tracker: MovementTracker::new(1000, 0.1),
|
2020-04-17 13:33:52 +00:00
|
|
|
memory: Default::default(),
|
|
|
|
graphics: Default::default(),
|
2020-04-23 17:15:17 +00:00
|
|
|
output: Default::default(),
|
2020-04-19 09:13:24 +00:00
|
|
|
used_ids: Default::default(),
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-02 09:37:12 +00:00
|
|
|
pub fn memory(&self) -> parking_lot::MutexGuard<Memory> {
|
|
|
|
self.memory.lock()
|
2020-04-23 17:15:17 +00:00
|
|
|
}
|
|
|
|
|
2020-05-02 09:37:12 +00:00
|
|
|
pub fn output(&self) -> parking_lot::MutexGuard<Output> {
|
|
|
|
self.output.lock()
|
2020-05-01 00:08:01 +00:00
|
|
|
}
|
|
|
|
|
2020-05-02 09:37:12 +00:00
|
|
|
pub fn input(&self) -> &GuiInput {
|
|
|
|
&self.input
|
2020-05-01 00:08:01 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 11:28:47 +00:00
|
|
|
/// Smoothed mouse velocity, in points per second
|
|
|
|
pub fn mouse_vel(&self) -> Vec2 {
|
|
|
|
self.mouse_tracker.velocity().unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
2020-04-28 21:05:22 +00:00
|
|
|
/// Raw input from last frame. Use `input()` instead.
|
|
|
|
pub fn last_raw_input(&self) -> &RawInput {
|
|
|
|
&self.last_raw_input
|
|
|
|
}
|
|
|
|
|
2020-04-17 13:33:52 +00:00
|
|
|
pub fn style(&self) -> Style {
|
2020-04-17 21:30:01 +00:00
|
|
|
*self.style.lock()
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_style(&self, style: Style) {
|
2020-04-17 21:30:01 +00:00
|
|
|
*self.style.lock() = style;
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
|
2020-05-03 11:28:47 +00:00
|
|
|
pub fn pixels_per_point(&self) -> f32 {
|
|
|
|
self.input.pixels_per_point
|
|
|
|
}
|
|
|
|
|
2020-05-02 09:37:12 +00:00
|
|
|
/// Useful for pixel-perfect rendering
|
|
|
|
pub fn round_to_pixel(&self, point: f32) -> f32 {
|
|
|
|
(point * self.input.pixels_per_point).round() / self.input.pixels_per_point
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn round_pos_to_pixels(&self, pos: Pos2) -> Pos2 {
|
|
|
|
pos2(self.round_to_pixel(pos.x), self.round_to_pixel(pos.y))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn round_vec_to_pixels(&self, vec: Vec2) -> Vec2 {
|
|
|
|
vec2(self.round_to_pixel(vec.x), self.round_to_pixel(vec.y))
|
|
|
|
}
|
|
|
|
|
2020-05-03 11:28:47 +00:00
|
|
|
pub fn begin_frame(&mut self, new_input: RawInput) {
|
|
|
|
if !self.last_raw_input.mouse_down || self.last_raw_input.mouse_pos.is_none() {
|
|
|
|
self.memory().active_id = None;
|
|
|
|
}
|
|
|
|
|
2020-04-19 09:13:24 +00:00
|
|
|
self.used_ids.lock().clear();
|
2020-05-03 11:28:47 +00:00
|
|
|
|
|
|
|
if let Some(mouse_pos) = new_input.mouse_pos {
|
|
|
|
self.mouse_tracker.add(new_input.time, mouse_pos);
|
|
|
|
} else {
|
|
|
|
self.mouse_tracker.clear();
|
|
|
|
}
|
|
|
|
self.input = GuiInput::from_last_and_new(&self.last_raw_input, &new_input);
|
|
|
|
self.input.mouse_velocity = self.mouse_vel();
|
|
|
|
self.last_raw_input = new_input;
|
2020-04-23 17:15:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn end_frame(&self) -> Output {
|
|
|
|
std::mem::take(&mut self.output.lock())
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
|
2020-04-20 21:33:16 +00:00
|
|
|
pub fn drain_paint_lists(&self) -> Vec<(Rect, PaintCmd)> {
|
2020-04-17 21:30:01 +00:00
|
|
|
let memory = self.memory.lock();
|
2020-04-25 20:49:57 +00:00
|
|
|
self.graphics.lock().drain(&memory.floating_order).collect()
|
2020-04-17 21:22:28 +00:00
|
|
|
}
|
|
|
|
|
2020-04-17 13:33:52 +00:00
|
|
|
/// Is the user interacting with anything?
|
|
|
|
pub fn any_active(&self) -> bool {
|
2020-04-17 21:30:01 +00:00
|
|
|
self.memory.lock().active_id.is_some()
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
|
2020-04-19 09:13:24 +00:00
|
|
|
/// Generate a id from the given source.
|
|
|
|
/// If it is not unique, an error will be printed at the given position.
|
2020-04-25 13:26:24 +00:00
|
|
|
pub fn make_unique_id<IdSource>(&self, source: IdSource, pos: Pos2) -> Id
|
2020-04-19 09:13:24 +00:00
|
|
|
where
|
2020-04-25 13:26:24 +00:00
|
|
|
IdSource: std::hash::Hash + std::fmt::Debug + Copy,
|
2020-04-19 09:13:24 +00:00
|
|
|
{
|
|
|
|
self.register_unique_id(Id::new(source), source, pos)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If the given Id is not unique, an error will be printed at the given position.
|
2020-04-25 13:26:24 +00:00
|
|
|
pub fn register_unique_id(&self, id: Id, source_name: impl std::fmt::Debug, pos: Pos2) -> Id {
|
2020-04-19 09:13:24 +00:00
|
|
|
if let Some(clash_pos) = self.used_ids.lock().insert(id, pos) {
|
|
|
|
if clash_pos.dist(pos) < 4.0 {
|
|
|
|
self.show_error(
|
|
|
|
pos,
|
|
|
|
&format!("use of non-unique ID {:?} (name clash?)", source_name),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
self.show_error(
|
|
|
|
clash_pos,
|
|
|
|
&format!("first use of non-unique ID {:?} (name clash?)", source_name),
|
|
|
|
);
|
|
|
|
self.show_error(
|
|
|
|
pos,
|
|
|
|
&format!(
|
|
|
|
"second use of non-unique ID {:?} (name clash?)",
|
|
|
|
source_name
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-26 20:30:24 +00:00
|
|
|
pub fn contains_mouse(&self, layer: Layer, clip_rect: &Rect, rect: &Rect) -> bool {
|
|
|
|
let rect = rect.intersect(clip_rect);
|
2020-04-22 18:01:49 +00:00
|
|
|
if let Some(mouse_pos) = self.input.mouse_pos {
|
|
|
|
rect.contains(mouse_pos) && layer == self.memory.lock().layer_at(mouse_pos)
|
2020-04-17 13:33:52 +00:00
|
|
|
} else {
|
|
|
|
false
|
2020-04-22 18:01:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-26 20:30:24 +00:00
|
|
|
pub fn interact(
|
|
|
|
&self,
|
|
|
|
layer: Layer,
|
|
|
|
clip_rect: &Rect,
|
|
|
|
rect: &Rect,
|
|
|
|
interaction_id: Option<Id>,
|
|
|
|
) -> InteractInfo {
|
|
|
|
let hovered = self.contains_mouse(layer, clip_rect, &rect);
|
2020-04-21 18:43:47 +00:00
|
|
|
|
2020-04-22 18:01:49 +00:00
|
|
|
let mut memory = self.memory.lock();
|
2020-04-21 18:43:47 +00:00
|
|
|
let active = interaction_id.is_some() && memory.active_id == interaction_id;
|
|
|
|
|
2020-04-22 18:01:49 +00:00
|
|
|
if self.input.mouse_pressed {
|
2020-04-21 18:43:47 +00:00
|
|
|
if hovered && interaction_id.is_some() {
|
|
|
|
if memory.active_id.is_some() {
|
|
|
|
// Already clicked something else this frame
|
|
|
|
InteractInfo {
|
2020-04-25 12:37:39 +00:00
|
|
|
rect: *rect,
|
2020-04-21 18:43:47 +00:00
|
|
|
hovered,
|
|
|
|
clicked: false,
|
|
|
|
active: false,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
memory.active_id = interaction_id;
|
|
|
|
InteractInfo {
|
2020-04-25 12:37:39 +00:00
|
|
|
rect: *rect,
|
2020-04-21 18:43:47 +00:00
|
|
|
hovered,
|
|
|
|
clicked: false,
|
|
|
|
active: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
InteractInfo {
|
2020-04-25 12:37:39 +00:00
|
|
|
rect: *rect,
|
2020-04-21 18:43:47 +00:00
|
|
|
hovered,
|
|
|
|
clicked: false,
|
|
|
|
active: false,
|
|
|
|
}
|
|
|
|
}
|
2020-04-22 18:01:49 +00:00
|
|
|
} else if self.input.mouse_released {
|
2020-04-21 18:43:47 +00:00
|
|
|
InteractInfo {
|
2020-04-25 12:37:39 +00:00
|
|
|
rect: *rect,
|
2020-04-21 18:43:47 +00:00
|
|
|
hovered,
|
|
|
|
clicked: hovered && active,
|
|
|
|
active,
|
|
|
|
}
|
2020-04-22 18:01:49 +00:00
|
|
|
} else if self.input.mouse_down {
|
2020-04-21 18:43:47 +00:00
|
|
|
InteractInfo {
|
2020-04-25 12:37:39 +00:00
|
|
|
rect: *rect,
|
2020-04-21 18:43:47 +00:00
|
|
|
hovered: hovered && active,
|
|
|
|
clicked: false,
|
|
|
|
active,
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-04-21 18:43:47 +00:00
|
|
|
InteractInfo {
|
2020-04-25 12:37:39 +00:00
|
|
|
rect: *rect,
|
2020-04-21 18:43:47 +00:00
|
|
|
hovered,
|
|
|
|
clicked: false,
|
|
|
|
active,
|
|
|
|
}
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-19 09:13:24 +00:00
|
|
|
|
|
|
|
pub fn show_error(&self, pos: Pos2, text: &str) {
|
|
|
|
let align = (Align::Min, Align::Min);
|
2020-04-27 14:53:14 +00:00
|
|
|
let layer = Layer::Debug;
|
2020-04-19 09:13:24 +00:00
|
|
|
let text_style = TextStyle::Monospace;
|
|
|
|
let font = &self.fonts[text_style];
|
2020-04-25 08:52:20 +00:00
|
|
|
let (text, size) = font.layout_multiline(text, f32::INFINITY);
|
2020-04-25 12:37:39 +00:00
|
|
|
let rect = align_rect(&Rect::from_min_size(pos, size), align);
|
2020-04-19 09:13:24 +00:00
|
|
|
self.add_paint_cmd(
|
|
|
|
layer,
|
|
|
|
PaintCmd::Rect {
|
|
|
|
corner_radius: 0.0,
|
|
|
|
fill_color: Some(color::gray(0, 240)),
|
|
|
|
outline: Some(Outline::new(1.0, color::RED)),
|
|
|
|
rect: rect.expand(2.0),
|
|
|
|
},
|
|
|
|
);
|
2020-04-25 13:45:38 +00:00
|
|
|
self.add_text(layer, rect.min, text_style, text, Some(color::RED));
|
2020-04-19 09:13:24 +00:00
|
|
|
}
|
|
|
|
|
2020-04-25 12:37:39 +00:00
|
|
|
pub fn debug_text(&self, pos: Pos2, text: &str) {
|
2020-04-27 14:53:14 +00:00
|
|
|
let layer = Layer::Debug;
|
2020-04-25 12:37:39 +00:00
|
|
|
let align = (Align::Min, Align::Min);
|
|
|
|
self.floating_text(
|
|
|
|
layer,
|
|
|
|
pos,
|
|
|
|
text,
|
|
|
|
TextStyle::Monospace,
|
|
|
|
align,
|
|
|
|
Some(color::YELLOW),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-04-19 09:13:24 +00:00
|
|
|
/// Show some text anywhere on screen.
|
|
|
|
/// To center the text at the given position, use `align: (Center, Center)`.
|
|
|
|
pub fn floating_text(
|
|
|
|
&self,
|
|
|
|
layer: Layer,
|
|
|
|
pos: Pos2,
|
|
|
|
text: &str,
|
|
|
|
text_style: TextStyle,
|
|
|
|
align: (Align, Align),
|
|
|
|
text_color: Option<Color>,
|
|
|
|
) -> Vec2 {
|
|
|
|
let font = &self.fonts[text_style];
|
2020-04-25 08:52:20 +00:00
|
|
|
let (text, size) = font.layout_multiline(text, f32::INFINITY);
|
2020-04-25 12:37:39 +00:00
|
|
|
let rect = align_rect(&Rect::from_min_size(pos, size), align);
|
2020-04-25 13:45:38 +00:00
|
|
|
self.add_text(layer, rect.min, text_style, text, text_color);
|
2020-04-19 09:13:24 +00:00
|
|
|
size
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Already layed out text.
|
|
|
|
pub fn add_text(
|
|
|
|
&self,
|
|
|
|
layer: Layer,
|
|
|
|
pos: Pos2,
|
|
|
|
text_style: TextStyle,
|
|
|
|
text: Vec<font::TextFragment>,
|
|
|
|
color: Option<Color>,
|
|
|
|
) {
|
|
|
|
let color = color.unwrap_or_else(|| self.style().text_color());
|
|
|
|
for fragment in text {
|
|
|
|
self.add_paint_cmd(
|
|
|
|
layer,
|
|
|
|
PaintCmd::Text {
|
|
|
|
color,
|
|
|
|
pos: pos + vec2(0.0, fragment.y_offset),
|
|
|
|
text: fragment.text,
|
|
|
|
text_style,
|
|
|
|
x_offsets: fragment.x_offsets,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_paint_cmd(&self, layer: Layer, paint_cmd: PaintCmd) {
|
2020-04-20 21:33:16 +00:00
|
|
|
self.graphics
|
|
|
|
.lock()
|
|
|
|
.layer(layer)
|
|
|
|
.push((Rect::everything(), paint_cmd))
|
2020-04-19 09:13:24 +00:00
|
|
|
}
|
2020-04-17 13:33:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
|
|
|
pub fn style_ui(&self, region: &mut Region) {
|
|
|
|
let mut style = self.style();
|
|
|
|
style.ui(region);
|
|
|
|
self.set_style(style);
|
|
|
|
}
|
|
|
|
}
|