use crate::*;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub(crate) struct State {
pub pivot_pos: Pos2,
pub pivot: Align2,
pub size: Vec2,
pub interactable: bool,
pub edges_padded_for_resize: bool,
}
impl State {
pub fn left_top_pos(&self) -> Pos2 {
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,
)
}
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,
);
}
pub fn rect(&self) -> Rect {
Rect::from_min_size(self.left_top_pos(), self.size)
}
}
#[must_use = "You should call .show()"]
#[derive(Clone, Copy, Debug)]
pub struct Area {
pub(crate) id: Id,
movable: bool,
interactable: bool,
enabled: bool,
constrain: bool,
constrain_rect: Option<Rect>,
order: Order,
default_pos: Option<Pos2>,
pivot: Align2,
anchor: Option<(Align2, Vec2)>,
new_pos: Option<Pos2>,
edges_padded_for_resize: bool,
}
impl Area {
pub fn new(id: impl Into<Id>) -> Self {
Self {
id: id.into(),
movable: true,
interactable: true,
constrain: false,
constrain_rect: None,
enabled: true,
order: Order::Middle,
default_pos: None,
new_pos: None,
pivot: Align2::LEFT_TOP,
anchor: None,
edges_padded_for_resize: false,
}
}
#[inline]
pub fn id(mut self, id: Id) -> Self {
self.id = id;
self
}
pub fn layer(&self) -> LayerId {
LayerId::new(self.order, self.id)
}
#[inline]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[inline]
pub fn movable(mut self, movable: bool) -> Self {
self.movable = movable;
self.interactable |= movable;
self
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn is_movable(&self) -> bool {
self.movable && self.enabled
}
#[inline]
pub fn interactable(mut self, interactable: bool) -> Self {
self.interactable = interactable;
self.movable &= interactable;
self
}
#[inline]
pub fn order(mut self, order: Order) -> Self {
self.order = order;
self
}
#[inline]
pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
self.default_pos = Some(default_pos.into());
self
}
#[inline]
pub fn fixed_pos(mut self, fixed_pos: impl Into<Pos2>) -> Self {
self.new_pos = Some(fixed_pos.into());
self.movable = false;
self
}
#[inline]
pub fn constrain(mut self, constrain: bool) -> Self {
self.constrain = constrain;
self
}
#[inline]
pub fn constrain_to(mut self, constrain_rect: Rect) -> Self {
self.constrain = true;
self.constrain_rect = Some(constrain_rect);
self
}
#[inline]
pub fn pivot(mut self, pivot: Align2) -> Self {
self.pivot = pivot;
self
}
#[inline]
pub fn current_pos(mut self, current_pos: impl Into<Pos2>) -> Self {
self.new_pos = Some(current_pos.into());
self
}
#[inline]
pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
self.anchor = Some((align, offset.into()));
self.movable(false)
}
pub(crate) fn get_pivot(&self) -> Align2 {
if let Some((pivot, _)) = self.anchor {
pivot
} else {
Align2::LEFT_TOP
}
}
#[inline]
pub(crate) fn edges_padded_for_resize(mut self, edges_padded_for_resize: bool) -> Self {
self.edges_padded_for_resize = edges_padded_for_resize;
self
}
}
pub(crate) struct Prepared {
layer_id: LayerId,
state: State,
move_response: Response,
enabled: bool,
constrain: bool,
constrain_rect: Option<Rect>,
temporarily_invisible: bool,
}
impl Area {
pub fn show<R>(
self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let prepared = self.begin(ctx);
let mut content_ui = prepared.content_ui(ctx);
let inner = add_contents(&mut content_ui);
let response = prepared.end(ctx, content_ui);
InnerResponse { inner, response }
}
pub(crate) fn begin(self, ctx: &Context) -> Prepared {
let Self {
id,
movable,
order,
interactable,
enabled,
default_pos,
new_pos,
pivot,
anchor,
constrain,
constrain_rect,
edges_padded_for_resize,
} = self;
let layer_id = LayerId::new(order, id);
let state = ctx
.memory(|mem| mem.areas().get(id).copied())
.map(|mut state| {
state.pivot = pivot;
state
});
let is_new = state.is_none();
if is_new {
ctx.request_repaint(); }
let mut state = state.unwrap_or_else(|| State {
pivot_pos: default_pos.unwrap_or_else(|| automatic_area_position(ctx)),
pivot,
size: Vec2::ZERO,
interactable,
edges_padded_for_resize,
});
state.pivot_pos = new_pos.unwrap_or(state.pivot_pos);
state.interactable = interactable;
state.edges_padded_for_resize = edges_padded_for_resize;
if let Some((anchor, offset)) = anchor {
let screen = ctx.available_rect();
state.set_left_top_pos(
anchor.align_size_within_rect(state.size, screen).left_top() + offset,
);
}
let mut move_response = {
let interact_id = layer_id.id.with("move");
let sense = if movable {
Sense::click_and_drag()
} else if interactable {
Sense::click() } else {
Sense::hover()
};
let move_response = ctx.interact(
Rect::EVERYTHING,
ctx.style().spacing.item_spacing,
layer_id,
interact_id,
state.rect(),
sense,
enabled,
);
if movable && move_response.dragged() {
state.pivot_pos += ctx.input(|i| i.pointer.delta());
}
if (move_response.dragged() || move_response.clicked())
|| pointer_pressed_on_area(ctx, layer_id)
|| !ctx.memory(|m| m.areas().visible_last_frame(&layer_id))
{
ctx.memory_mut(|m| m.areas_mut().move_to_top(layer_id));
ctx.request_repaint();
}
move_response
};
if constrain {
state.set_left_top_pos(
ctx.constrain_window_rect_to_area(state.rect(), constrain_rect)
.min,
);
}
state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
move_response = move_response.with_new_rect(state.rect());
Prepared {
layer_id,
state,
move_response,
enabled,
constrain,
constrain_rect,
temporarily_invisible: is_new,
}
}
pub fn show_open_close_animation(&self, ctx: &Context, frame: &Frame, is_open: bool) {
let visibility_factor = ctx.animate_bool(self.id.with("close_animation"), is_open);
if is_open {
return;
}
if visibility_factor <= 0.0 {
return;
}
let layer_id = LayerId::new(self.order, self.id);
let area_rect = ctx.memory(|mem| mem.areas().get(self.id).map(|area| area.rect()));
if let Some(area_rect) = area_rect {
let clip_rect = ctx.available_rect();
let painter = Painter::new(ctx.clone(), layer_id, clip_rect);
let frame = frame.multiply_with_opacity(visibility_factor);
painter.add(frame.paint(area_rect));
}
}
}
impl Prepared {
pub(crate) fn state(&self) -> &State {
&self.state
}
pub(crate) fn state_mut(&mut self) -> &mut State {
&mut self.state
}
pub(crate) fn constrain(&self) -> bool {
self.constrain
}
pub(crate) fn constrain_rect(&self) -> Option<Rect> {
self.constrain_rect
}
pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
let screen_rect = ctx.screen_rect();
let constrain_rect = if let Some(constrain_rect) = self.constrain_rect {
constrain_rect.intersect(screen_rect) } else {
let central_area = ctx.available_rect();
let is_within_central_area = central_area.contains_rect(self.state.rect().shrink(1.0));
if is_within_central_area {
central_area } else {
screen_rect
}
};
let max_rect = Rect::from_min_max(
self.state.left_top_pos(),
constrain_rect
.max
.at_least(self.state.left_top_pos() + Vec2::splat(32.0)),
);
let shadow_radius = ctx.style().visuals.window_shadow.extrusion; let clip_rect_margin = ctx.style().visuals.clip_rect_margin.max(shadow_radius);
let clip_rect = Rect::from_min_max(self.state.left_top_pos(), constrain_rect.max)
.expand(clip_rect_margin)
.intersect(constrain_rect);
let mut ui = Ui::new(
ctx.clone(),
self.layer_id,
self.layer_id.id,
max_rect,
clip_rect,
);
ui.set_enabled(self.enabled);
ui.set_visible(!self.temporarily_invisible);
ui
}
#[allow(clippy::needless_pass_by_value)] pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
let Self {
layer_id,
mut state,
move_response,
enabled: _,
constrain: _,
constrain_rect: _,
temporarily_invisible: _,
} = self;
state.size = content_ui.min_size();
ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state));
move_response
}
}
fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
if let Some(pointer_pos) = ctx.pointer_interact_pos() {
let any_pressed = ctx.input(|i| i.pointer.any_pressed());
any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
} else {
false
}
}
fn automatic_area_position(ctx: &Context) -> Pos2 {
let mut existing: Vec<Rect> = ctx.memory(|mem| {
mem.areas()
.visible_windows()
.into_iter()
.map(State::rect)
.collect()
});
existing.sort_by_key(|r| r.left().round() as i32);
let available_rect = ctx.available_rect();
let spacing = 16.0;
let left = available_rect.left() + spacing;
let top = available_rect.top() + spacing;
if existing.is_empty() {
return pos2(left, top);
}
let mut column_bbs = vec![existing[0]];
for &rect in &existing {
let current_column_bb = column_bbs.last_mut().unwrap();
if rect.left() < current_column_bb.right() {
*current_column_bb = current_column_bb.union(rect);
} else {
column_bbs.push(rect);
}
}
{
let mut x = left;
for col_bb in &column_bbs {
let available = col_bb.left() - x;
if available >= 300.0 {
return pos2(x, top);
}
x = col_bb.right() + spacing;
}
}
for col_bb in &column_bbs {
if col_bb.bottom() < available_rect.center().y {
return pos2(col_bb.left(), col_bb.bottom() + spacing);
}
}
let rightmost = column_bbs.last().unwrap().right();
if rightmost + 200.0 < available_rect.right() {
return pos2(rightmost + spacing, top);
}
let mut best_pos = pos2(left, column_bbs[0].bottom() + spacing);
for col_bb in &column_bbs {
let col_pos = pos2(col_bb.left(), col_bb.bottom() + spacing);
if col_pos.y < best_pos.y {
best_pos = col_pos;
}
}
best_pos
}