2020-05-10 11:14:52 +00:00
|
|
|
//! Area is a `Ui` that has no parent, it floats on the background.
|
|
|
|
//! It has no frame or own size. It is potentioally movable.
|
|
|
|
//! It is the foundation for windows and popups.
|
2020-04-25 20:49:57 +00:00
|
|
|
|
|
|
|
use std::{fmt::Debug, hash::Hash, sync::Arc};
|
|
|
|
|
|
|
|
use crate::*;
|
|
|
|
|
2020-05-07 08:47:03 +00:00
|
|
|
#[derive(Clone, Copy, Debug, serde_derive::Deserialize, serde_derive::Serialize)]
|
2020-05-02 09:37:12 +00:00
|
|
|
pub(crate) struct State {
|
2020-04-25 20:49:57 +00:00
|
|
|
/// Last known pos
|
|
|
|
pub pos: Pos2,
|
|
|
|
|
|
|
|
/// Last know size. Used for catching clicks.
|
|
|
|
pub size: Vec2,
|
2020-05-03 11:28:47 +00:00
|
|
|
|
2020-05-10 12:27:02 +00:00
|
|
|
/// If false, clicks goes stright throught to what is behind us.
|
|
|
|
/// Good for tooltips etc.
|
|
|
|
pub interactable: bool,
|
|
|
|
|
2020-05-10 11:14:52 +00:00
|
|
|
/// You can throw a moveable Area. It's fun.
|
|
|
|
/// TODO: separate out moveable to container?
|
2020-05-03 11:28:47 +00:00
|
|
|
#[serde(skip)]
|
|
|
|
pub vel: Vec2,
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
2020-05-10 11:14:52 +00:00
|
|
|
pub struct Area {
|
2020-04-25 20:49:57 +00:00
|
|
|
id: Id,
|
|
|
|
movable: bool,
|
2020-05-10 12:27:02 +00:00
|
|
|
interactable: bool,
|
|
|
|
order: Order,
|
2020-04-25 20:49:57 +00:00
|
|
|
default_pos: Option<Pos2>,
|
2020-05-10 11:08:08 +00:00
|
|
|
fixed_pos: Option<Pos2>,
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 11:14:52 +00:00
|
|
|
impl Area {
|
2020-04-25 20:49:57 +00:00
|
|
|
pub fn new(id_source: impl Hash) -> Self {
|
|
|
|
Self {
|
|
|
|
id: Id::new(id_source),
|
|
|
|
movable: true,
|
2020-05-10 12:27:02 +00:00
|
|
|
interactable: true,
|
|
|
|
order: Order::Middle,
|
2020-04-25 20:49:57 +00:00
|
|
|
default_pos: None,
|
2020-05-10 11:08:08 +00:00
|
|
|
fixed_pos: None,
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn movable(mut self, movable: bool) -> Self {
|
|
|
|
self.movable = movable;
|
2020-05-10 12:27:02 +00:00
|
|
|
self.interactable |= movable;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// If false, clicks goes stright throught to what is behind us.
|
|
|
|
/// Good for tooltips etc.
|
|
|
|
pub fn interactable(mut self, interactable: bool) -> Self {
|
|
|
|
self.interactable = interactable;
|
|
|
|
self.movable &= interactable;
|
2020-04-25 20:49:57 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-05-10 12:27:02 +00:00
|
|
|
/// `order(Order::Foreground)` for an Area that should always be on top
|
|
|
|
pub fn order(mut self, order: Order) -> Self {
|
|
|
|
self.order = order;
|
2020-05-10 11:08:08 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-04-25 20:49:57 +00:00
|
|
|
pub fn default_pos(mut self, default_pos: Pos2) -> Self {
|
|
|
|
self.default_pos = Some(default_pos);
|
|
|
|
self
|
|
|
|
}
|
2020-05-10 11:08:08 +00:00
|
|
|
|
|
|
|
pub fn fixed_pos(mut self, fixed_pos: Pos2) -> Self {
|
|
|
|
self.default_pos = Some(fixed_pos);
|
|
|
|
self.fixed_pos = Some(fixed_pos);
|
|
|
|
self.movable = false;
|
|
|
|
self
|
|
|
|
}
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
|
|
|
|
2020-05-10 11:14:52 +00:00
|
|
|
impl Area {
|
2020-05-10 11:08:08 +00:00
|
|
|
// TODO
|
|
|
|
// pub fn show(self, ui: &Ui, add_contents: impl FnOnce(&mut Ui)) {
|
|
|
|
// let default_pos = self.default_pos.unwrap_or_else(|| ui.top_left() + pos2(100.0, 100.0)); // TODO
|
|
|
|
// }
|
|
|
|
|
|
|
|
pub fn show(self, ctx: &Arc<Context>, add_contents: impl FnOnce(&mut Ui)) -> InteractInfo {
|
2020-05-10 11:14:52 +00:00
|
|
|
let Area {
|
2020-05-10 11:08:08 +00:00
|
|
|
id,
|
|
|
|
movable,
|
2020-05-10 12:27:02 +00:00
|
|
|
order,
|
|
|
|
interactable,
|
2020-05-10 11:08:08 +00:00
|
|
|
default_pos,
|
|
|
|
fixed_pos,
|
|
|
|
} = self;
|
|
|
|
|
|
|
|
let default_pos = default_pos.unwrap_or_else(|| pos2(100.0, 100.0)); // TODO
|
2020-05-10 11:14:52 +00:00
|
|
|
let id = ctx.register_unique_id(id, "Area", default_pos);
|
2020-05-10 12:27:02 +00:00
|
|
|
let layer = Layer { order, id };
|
2020-04-25 20:49:57 +00:00
|
|
|
|
2020-05-10 17:02:17 +00:00
|
|
|
let mut state = ctx.memory().areas.get(id).unwrap_or_else(|| State {
|
|
|
|
pos: default_pos,
|
|
|
|
size: Vec2::zero(),
|
|
|
|
interactable,
|
|
|
|
vel: Vec2::zero(),
|
|
|
|
});
|
2020-05-10 11:08:08 +00:00
|
|
|
state.pos = fixed_pos.unwrap_or(state.pos);
|
2020-04-25 20:49:57 +00:00
|
|
|
state.pos = state.pos.round();
|
|
|
|
|
2020-05-08 20:42:31 +00:00
|
|
|
let mut ui = Ui::new(
|
2020-04-25 20:49:57 +00:00
|
|
|
ctx.clone(),
|
|
|
|
layer,
|
|
|
|
id,
|
|
|
|
Rect::from_min_size(state.pos, Vec2::infinity()),
|
|
|
|
);
|
2020-05-08 20:42:31 +00:00
|
|
|
add_contents(&mut ui);
|
2020-05-12 20:21:04 +00:00
|
|
|
state.size = (ui.child_bounds().max - state.pos).ceil();
|
2020-04-25 20:49:57 +00:00
|
|
|
|
|
|
|
let rect = Rect::from_min_size(state.pos, state.size);
|
2020-05-10 11:08:08 +00:00
|
|
|
let clip_rect = Rect::everything(); // TODO: get from context
|
|
|
|
|
|
|
|
let interact_id = if movable { Some(id.with("move")) } else { None };
|
|
|
|
let move_interact = ctx.interact(layer, clip_rect, rect, interact_id);
|
2020-04-25 20:49:57 +00:00
|
|
|
|
2020-05-03 11:28:47 +00:00
|
|
|
let input = ctx.input();
|
2020-04-25 20:49:57 +00:00
|
|
|
if move_interact.active {
|
2020-05-03 11:28:47 +00:00
|
|
|
state.pos += input.mouse_move;
|
|
|
|
state.vel = input.mouse_velocity;
|
|
|
|
} else {
|
|
|
|
let stop_speed = 20.0; // Pixels per second.
|
|
|
|
let friction_coeff = 1000.0; // Pixels per second squared.
|
|
|
|
|
|
|
|
let friction = friction_coeff * input.dt;
|
|
|
|
if friction > state.vel.length() || state.vel.length() < stop_speed {
|
|
|
|
state.vel = Vec2::zero();
|
|
|
|
} else {
|
|
|
|
state.vel -= friction * state.vel.normalized();
|
|
|
|
state.pos += state.vel * input.dt;
|
|
|
|
}
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Constrain to screen:
|
|
|
|
let margin = 32.0;
|
|
|
|
state.pos = state.pos.max(pos2(margin - state.size.x, 0.0));
|
|
|
|
state.pos = state.pos.min(pos2(
|
2020-05-04 19:35:16 +00:00
|
|
|
ctx.input().screen_size.x - margin,
|
|
|
|
ctx.input().screen_size.y - margin,
|
2020-04-25 20:49:57 +00:00
|
|
|
));
|
|
|
|
|
|
|
|
state.pos = state.pos.round();
|
|
|
|
|
2020-05-05 17:33:02 +00:00
|
|
|
// ctx.debug_rect(
|
|
|
|
// Rect::from_min_size(state.pos, state.size),
|
2020-05-10 11:14:52 +00:00
|
|
|
// &format!("Area size: {:?}", state.size),
|
2020-05-05 17:33:02 +00:00
|
|
|
// );
|
|
|
|
|
2020-05-12 14:49:43 +00:00
|
|
|
if move_interact.active
|
|
|
|
|| mouse_pressed_on_area(ctx, layer)
|
|
|
|
|| !ctx.memory().areas.visible_last_frame(&layer)
|
|
|
|
{
|
2020-05-10 17:02:17 +00:00
|
|
|
ctx.memory().areas.move_to_top(layer);
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
2020-05-10 17:02:17 +00:00
|
|
|
ctx.memory().areas.set_state(layer, state);
|
2020-05-10 11:08:08 +00:00
|
|
|
|
|
|
|
move_interact
|
2020-04-25 20:49:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 12:27:02 +00:00
|
|
|
fn mouse_pressed_on_area(ctx: &Context, layer: Layer) -> bool {
|
2020-05-04 19:35:16 +00:00
|
|
|
if let Some(mouse_pos) = ctx.input().mouse_pos {
|
2020-05-10 12:27:02 +00:00
|
|
|
ctx.input().mouse_pressed && ctx.memory().layer_at(mouse_pos) == Some(layer)
|
2020-04-25 20:49:57 +00:00
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|