|
|
@ -8,21 +8,23 @@ use crate::*; |
|
|
|
///
|
|
|
|
/// Areas back [`crate::Window`]s and other floating containers,
|
|
|
|
/// like tooltips and the popups of [`crate::ComboBox`].
|
|
|
|
///
|
|
|
|
/// Area state is intentionally NOT persisted between sessions,
|
|
|
|
/// so that a bad tooltip or menu size won't be remembered forever.
|
|
|
|
/// A resizable [`Window`] remembers the size the user picked using
|
|
|
|
/// the state in the [`Resize`] container.
|
|
|
|
#[derive(Clone, Copy, Debug)] |
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] |
|
|
|
pub struct AreaState { |
|
|
|
/// Last known position of the pivot.
|
|
|
|
pub pivot_pos: Pos2, |
|
|
|
pub pivot_pos: Option<Pos2>, |
|
|
|
|
|
|
|
/// The anchor point of the area, i.e. where on the area the [`Self::pivot_pos`] refers to.
|
|
|
|
pub pivot: Align2, |
|
|
|
|
|
|
|
/// Last known size.
|
|
|
|
pub size: Vec2, |
|
|
|
///
|
|
|
|
/// Area size is intentionally NOT persisted between sessions,
|
|
|
|
/// so that a bad tooltip or menu size won't be remembered forever.
|
|
|
|
/// A resizable [`Window`] remembers the size the user picked using
|
|
|
|
/// the state in the [`Resize`] container.
|
|
|
|
#[cfg_attr(feature = "serde", serde(skip))] |
|
|
|
pub size: Option<Vec2>, |
|
|
|
|
|
|
|
/// If false, clicks goes straight through to what is behind us. Useful for tooltips etc.
|
|
|
|
pub interactable: bool, |
|
|
@ -30,7 +32,20 @@ pub struct AreaState { |
|
|
|
/// At what time was this area first shown?
|
|
|
|
///
|
|
|
|
/// Used to fade in the area.
|
|
|
|
pub last_became_visible_at: f64, |
|
|
|
#[cfg_attr(feature = "serde", serde(skip))] |
|
|
|
pub last_became_visible_at: Option<f64>, |
|
|
|
} |
|
|
|
|
|
|
|
impl Default for AreaState { |
|
|
|
fn default() -> Self { |
|
|
|
Self { |
|
|
|
pivot_pos: None, |
|
|
|
pivot: Align2::LEFT_TOP, |
|
|
|
size: None, |
|
|
|
interactable: true, |
|
|
|
last_became_visible_at: None, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
impl AreaState { |
|
|
@ -42,23 +57,27 @@ impl AreaState { |
|
|
|
|
|
|
|
/// The left top positions of the area.
|
|
|
|
pub fn left_top_pos(&self) -> Pos2 { |
|
|
|
let pivot_pos = self.pivot_pos.unwrap_or_default(); |
|
|
|
let size = self.size.unwrap_or_default(); |
|
|
|
pos2( |
|
|
|
self.pivot_pos.x - self.pivot.x().to_factor() * self.size.x, |
|
|
|
self.pivot_pos.y - self.pivot.y().to_factor() * self.size.y, |
|
|
|
pivot_pos.x - self.pivot.x().to_factor() * size.x, |
|
|
|
pivot_pos.y - self.pivot.y().to_factor() * size.y, |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
/// Move the left top positions of the area.
|
|
|
|
pub fn set_left_top_pos(&mut self, pos: Pos2) { |
|
|
|
self.pivot_pos = pos2( |
|
|
|
pos.x + self.pivot.x().to_factor() * self.size.x, |
|
|
|
pos.y + self.pivot.y().to_factor() * self.size.y, |
|
|
|
); |
|
|
|
let size = self.size.unwrap_or_default(); |
|
|
|
self.pivot_pos = Some(pos2( |
|
|
|
pos.x + self.pivot.x().to_factor() * size.x, |
|
|
|
pos.y + self.pivot.y().to_factor() * size.y, |
|
|
|
)); |
|
|
|
} |
|
|
|
|
|
|
|
/// Where the area is on screen.
|
|
|
|
pub fn rect(&self) -> Rect { |
|
|
|
Rect::from_min_size(self.left_top_pos(), self.size) |
|
|
|
let size = self.size.unwrap_or_default(); |
|
|
|
Rect::from_min_size(self.left_top_pos(), size) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -371,16 +390,28 @@ impl Area { |
|
|
|
|
|
|
|
let layer_id = LayerId::new(order, id); |
|
|
|
|
|
|
|
let state = AreaState::load(ctx, id).map(|mut state| { |
|
|
|
// override the saved state with the correct value
|
|
|
|
state.pivot = pivot; |
|
|
|
state |
|
|
|
let state = AreaState::load(ctx, id); |
|
|
|
let mut sizing_pass = state.is_none(); |
|
|
|
let mut state = state.unwrap_or(AreaState { |
|
|
|
pivot_pos: None, |
|
|
|
pivot, |
|
|
|
size: None, |
|
|
|
interactable, |
|
|
|
last_became_visible_at: None, |
|
|
|
}); |
|
|
|
let is_new = state.is_none(); |
|
|
|
if is_new { |
|
|
|
ctx.request_repaint(); // if we don't know the previous size we are likely drawing the area in the wrong place
|
|
|
|
state.pivot = pivot; |
|
|
|
state.interactable = interactable; |
|
|
|
if let Some(new_pos) = new_pos { |
|
|
|
state.pivot_pos = Some(new_pos); |
|
|
|
} |
|
|
|
let mut state = state.unwrap_or_else(|| { |
|
|
|
state.pivot_pos.get_or_insert_with(|| { |
|
|
|
default_pos.unwrap_or_else(|| automatic_area_position(ctx, layer_id)) |
|
|
|
}); |
|
|
|
state.interactable = interactable; |
|
|
|
|
|
|
|
let size = *state.size.get_or_insert_with(|| { |
|
|
|
sizing_pass = true; |
|
|
|
|
|
|
|
// during the sizing pass we will use this as the max size
|
|
|
|
let mut size = default_size; |
|
|
|
|
|
|
@ -396,28 +427,20 @@ impl Area { |
|
|
|
size = size.at_most(constrain_rect.size()); |
|
|
|
} |
|
|
|
|
|
|
|
AreaState { |
|
|
|
pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)), |
|
|
|
pivot, |
|
|
|
size, |
|
|
|
interactable, |
|
|
|
last_became_visible_at: ctx.input(|i| i.time), |
|
|
|
} |
|
|
|
size |
|
|
|
}); |
|
|
|
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos); |
|
|
|
state.interactable = interactable; |
|
|
|
|
|
|
|
// TODO(emilk): if last frame was sizing pass, it should be considered invisible for smmother fade-in
|
|
|
|
// TODO(emilk): if last frame was sizing pass, it should be considered invisible for smoother fade-in
|
|
|
|
let visible_last_frame = ctx.memory(|mem| mem.areas().visible_last_frame(&layer_id)); |
|
|
|
|
|
|
|
if !visible_last_frame { |
|
|
|
state.last_became_visible_at = ctx.input(|i| i.time); |
|
|
|
if !visible_last_frame || state.last_became_visible_at.is_none() { |
|
|
|
state.last_became_visible_at = Some(ctx.input(|i| i.time)); |
|
|
|
} |
|
|
|
|
|
|
|
if let Some((anchor, offset)) = anchor { |
|
|
|
state.set_left_top_pos( |
|
|
|
anchor |
|
|
|
.align_size_within_rect(state.size, constrain_rect) |
|
|
|
.align_size_within_rect(size, constrain_rect) |
|
|
|
.left_top() |
|
|
|
+ offset, |
|
|
|
); |
|
|
@ -446,7 +469,9 @@ impl Area { |
|
|
|
}); |
|
|
|
|
|
|
|
if movable && move_response.dragged() { |
|
|
|
state.pivot_pos += move_response.drag_delta(); |
|
|
|
if let Some(pivot_pos) = &mut state.pivot_pos { |
|
|
|
*pivot_pos += move_response.drag_delta(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (move_response.dragged() || move_response.clicked()) |
|
|
@ -481,7 +506,7 @@ impl Area { |
|
|
|
enabled, |
|
|
|
constrain, |
|
|
|
constrain_rect, |
|
|
|
sizing_pass: is_new, |
|
|
|
sizing_pass, |
|
|
|
fade_in, |
|
|
|
} |
|
|
|
} |
|
|
@ -505,7 +530,7 @@ impl Prepared { |
|
|
|
} |
|
|
|
|
|
|
|
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui { |
|
|
|
let max_rect = Rect::from_min_size(self.state.left_top_pos(), self.state.size); |
|
|
|
let max_rect = self.state.rect(); |
|
|
|
|
|
|
|
let clip_rect = self.constrain_rect; // Don't paint outside our bounds
|
|
|
|
|
|
|
@ -519,13 +544,14 @@ impl Prepared { |
|
|
|
); |
|
|
|
|
|
|
|
if self.fade_in { |
|
|
|
let age = |
|
|
|
ctx.input(|i| (i.time - self.state.last_became_visible_at) as f32 + i.predicted_dt); |
|
|
|
let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0); |
|
|
|
let opacity = emath::easing::cubic_out(opacity); // slow fade-out = quick fade-in
|
|
|
|
ui.multiply_opacity(opacity); |
|
|
|
if opacity < 1.0 { |
|
|
|
ctx.request_repaint(); |
|
|
|
if let Some(last_became_visible_at) = self.state.last_became_visible_at { |
|
|
|
let age = ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt); |
|
|
|
let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0); |
|
|
|
let opacity = emath::easing::cubic_out(opacity); // slow fade-out = quick fade-in
|
|
|
|
ui.multiply_opacity(opacity); |
|
|
|
if opacity < 1.0 { |
|
|
|
ctx.request_repaint(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -545,10 +571,11 @@ impl Prepared { |
|
|
|
layer_id, |
|
|
|
mut state, |
|
|
|
move_response: mut response, |
|
|
|
sizing_pass, |
|
|
|
.. |
|
|
|
} = self; |
|
|
|
|
|
|
|
state.size = content_ui.min_size(); |
|
|
|
state.size = Some(content_ui.min_size()); |
|
|
|
|
|
|
|
// Make sure we report back the correct size.
|
|
|
|
// Very important after the initial sizing pass, when the initial estimate of the size is way off.
|
|
|
@ -558,6 +585,11 @@ impl Prepared { |
|
|
|
|
|
|
|
ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state)); |
|
|
|
|
|
|
|
if sizing_pass { |
|
|
|
// If we didn't know the size, we were likely drawing the area in the wrong place.
|
|
|
|
ctx.request_repaint(); |
|
|
|
} |
|
|
|
|
|
|
|
response |
|
|
|
} |
|
|
|
} |
|
|
@ -571,12 +603,13 @@ fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fn automatic_area_position(ctx: &Context) -> Pos2 { |
|
|
|
fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 { |
|
|
|
let mut existing: Vec<Rect> = ctx.memory(|mem| { |
|
|
|
mem.areas() |
|
|
|
.visible_windows() |
|
|
|
.into_iter() |
|
|
|
.map(AreaState::rect) |
|
|
|
.filter(|(id, _)| id != &layer_id) // ignore ourselves
|
|
|
|
.filter(|(_, state)| state.pivot_pos.is_some() && state.size.is_some()) |
|
|
|
.map(|(_, state)| state.rect()) |
|
|
|
.collect() |
|
|
|
}); |
|
|
|
existing.sort_by_key(|r| r.left().round() as i32); |
|
|
|