use core::f32;
use egui::{
CentralPanel, Color32, Id, InnerResponse, Margin, Pos2, Rect, Rounding, Sense, Shadow, Stroke, Vec2
};
pub struct CustomFrame {
pub sizebox: Margin,
pub caption: Rect,
pub inner_margin: Margin,
pub rounding: Rounding,
pub shadow: Shadow,
}
impl CustomFrame {
#[inline]
pub fn sizebox(mut self, sizebox: Margin) -> Self {
self.sizebox = sizebox;
self
}
#[inline]
pub fn caption(mut self, caption: Rect) -> Self {
self.caption = caption;
self
}
#[inline]
pub fn rounding(mut self, rounding: Rounding) -> Self {
self.rounding = rounding;
self
}
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = shadow;
self
}
pub fn show<R>(&self, ctx: &egui::Context, add_content: impl FnOnce(&mut egui::Ui) -> R) -> InnerResponse<R> {
let is_maximized = ctx.input(|i| i.viewport().maximized.unwrap_or(false));
let shadow_width = if is_maximized { 0.0 } else { self.shadow.blur + self.shadow.spread };
let panel_frame = egui::Frame {
fill: ctx.style().visuals.panel_fill,
rounding: if is_maximized { Rounding::ZERO } else { self.rounding },
stroke: Stroke::NONE,
outer_margin: shadow_width.into(), inner_margin: self.inner_margin,
shadow: if is_maximized { Shadow::NONE } else { self.shadow },
..Default::default()
};
CentralPanel::default().frame(panel_frame).show(ctx, |ui| {
let mut view_rect = ui.clip_rect();
view_rect = Rect::from_min_size(
Pos2::ZERO,
Vec2::new(view_rect.width(), view_rect.height()),
)
.shrink(shadow_width);
let sizebox_inner = view_rect - self.sizebox;
let caption_rect = self.caption
.translate(Vec2::new(shadow_width, shadow_width))
.intersect(sizebox_inner);
let pos = ui.input(
|i|
i.pointer
.interact_pos()
.unwrap_or(Pos2::new(f32::MIN, f32::MIN))
);
let sizebox_response = if view_rect.contains(pos) &&
!sizebox_inner.contains(pos) &&
!is_maximized {
Some(ui.interact(
view_rect,
Id::new("custom_frame_sizebox"),
Sense::drag(),
))
} else {
None
};
let caption_response = if caption_rect.contains(pos) {
Some(ui.interact(
caption_rect,
Id::new("custom_frame_caption"),
Sense::click_and_drag(),
))
} else {
None
};
if let Some(sizebox_response) = sizebox_response {
let drag = sizebox_response.drag_started();
if pos.x < sizebox_inner.left() {
if pos.y < sizebox_inner.top() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeNorthWest);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::NorthWest)
);
}
} else if pos.y > sizebox_inner.bottom() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeSouthWest);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::SouthWest)
);
}
} else {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeWest);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::West)
);
}
}
} else if pos.x > sizebox_inner.right() {
if pos.y < sizebox_inner.top() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeNorthEast);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::NorthEast)
);
}
} else if pos.y > sizebox_inner.bottom() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeSouthEast);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::SouthEast)
);
}
} else {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeEast);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::East)
);
}
}
} else {
if pos.y < sizebox_inner.top() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeNorth);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::North)
);
}
} else if pos.y > sizebox_inner.bottom() {
ui.ctx().set_cursor_icon(egui::CursorIcon::ResizeSouth);
if drag {
ui.ctx().send_viewport_cmd(
egui::ViewportCommand::BeginResize(egui::ResizeDirection::South)
);
}
}
}
}
if let Some(caption_response) = caption_response {
if caption_response.drag_started() {
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
if caption_response.double_clicked() {
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Maximized(!is_maximized));
}
}
add_content(ui)
})
}
}
impl Default for CustomFrame {
fn default() -> Self {
Self {
sizebox: Margin::same(4.0),
caption: Rect::from_min_max(Pos2::new(4.0, 4.0), Pos2::new(f32::MAX, 44.0)),
inner_margin: 10.0.into(),
rounding: 10.0.into(),
shadow: Shadow {
offset: Vec2::ZERO,
blur: 18.0,
spread: 2.0,
color: Color32::from_black_alpha(0x20)
},
}
}
}