use crate::{
InnerResponse, Response, Sense, Style, Ui, UiBuilder, UiKind, UiStackInfo, epaint,
layers::ShapeIdx,
};
use epaint::{Color32, CornerRadius, Margin, MarginF32, Rect, Shadow, Shape, Stroke};
#[doc(alias = "border")]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[must_use = "You should call .show()"]
pub struct Frame {
#[doc(alias = "padding")]
pub inner_margin: Margin,
#[doc(alias = "background")]
pub fill: Color32,
#[doc(alias = "border")]
pub stroke: Stroke,
pub corner_radius: CornerRadius,
pub outer_margin: Margin,
pub shadow: Shadow,
}
#[test]
fn frame_size() {
assert_eq!(
std::mem::size_of::<Frame>(),
32,
"Frame changed size! If it shrank - good! Update this test. If it grew - bad! Try to find a way to avoid it."
);
assert!(
std::mem::size_of::<Frame>() <= 64,
"Frame is getting way too big!"
);
}
impl Frame {
pub const NONE: Self = Self {
inner_margin: Margin::ZERO,
stroke: Stroke::NONE,
fill: Color32::TRANSPARENT,
corner_radius: CornerRadius::ZERO,
outer_margin: Margin::ZERO,
shadow: Shadow::NONE,
};
pub const fn new() -> Self {
Self::NONE
}
#[deprecated = "Use `Frame::NONE` or `Frame::new()` instead."]
pub const fn none() -> Self {
Self::NONE
}
pub fn group(style: &Style) -> Self {
Self::new()
.inner_margin(6)
.corner_radius(style.visuals.widgets.noninteractive.corner_radius)
.stroke(style.visuals.widgets.noninteractive.bg_stroke)
}
pub fn side_top_panel(style: &Style) -> Self {
Self::new()
.inner_margin(Margin::symmetric(8, 2))
.fill(style.visuals.panel_fill)
}
pub fn central_panel(style: &Style) -> Self {
Self::new().inner_margin(8).fill(style.visuals.panel_fill)
}
pub fn window(style: &Style) -> Self {
Self::new()
.inner_margin(style.spacing.window_margin)
.corner_radius(style.visuals.window_corner_radius)
.shadow(style.visuals.window_shadow)
.fill(style.visuals.window_fill())
.stroke(style.visuals.window_stroke())
}
pub fn menu(style: &Style) -> Self {
Self::new()
.inner_margin(style.spacing.menu_margin)
.corner_radius(style.visuals.menu_corner_radius)
.shadow(style.visuals.popup_shadow)
.fill(style.visuals.window_fill())
.stroke(style.visuals.window_stroke())
}
pub fn popup(style: &Style) -> Self {
Self::new()
.inner_margin(style.spacing.menu_margin)
.corner_radius(style.visuals.menu_corner_radius)
.shadow(style.visuals.popup_shadow)
.fill(style.visuals.window_fill())
.stroke(style.visuals.window_stroke())
}
pub fn canvas(style: &Style) -> Self {
Self::new()
.inner_margin(2)
.corner_radius(style.visuals.widgets.noninteractive.corner_radius)
.fill(style.visuals.extreme_bg_color)
.stroke(style.visuals.window_stroke())
}
pub fn dark_canvas(style: &Style) -> Self {
Self::canvas(style).fill(Color32::from_black_alpha(250))
}
}
impl Frame {
#[doc(alias = "padding")]
#[inline]
pub fn inner_margin(mut self, inner_margin: impl Into<Margin>) -> Self {
self.inner_margin = inner_margin.into();
self
}
#[doc(alias = "background")]
#[inline]
pub fn fill(mut self, fill: Color32) -> Self {
self.fill = fill;
self
}
#[inline]
pub fn stroke(mut self, stroke: impl Into<Stroke>) -> Self {
self.stroke = stroke.into();
self
}
#[inline]
pub fn corner_radius(mut self, corner_radius: impl Into<CornerRadius>) -> Self {
self.corner_radius = corner_radius.into();
self
}
#[inline]
#[deprecated = "Renamed to `corner_radius`"]
pub fn rounding(self, corner_radius: impl Into<CornerRadius>) -> Self {
self.corner_radius(corner_radius)
}
#[inline]
pub fn outer_margin(mut self, outer_margin: impl Into<Margin>) -> Self {
self.outer_margin = outer_margin.into();
self
}
#[inline]
pub fn shadow(mut self, shadow: Shadow) -> Self {
self.shadow = shadow;
self
}
#[inline]
pub fn multiply_with_opacity(mut self, opacity: f32) -> Self {
self.fill = self.fill.gamma_multiply(opacity);
self.stroke.color = self.stroke.color.gamma_multiply(opacity);
self.shadow.color = self.shadow.color.gamma_multiply(opacity);
self
}
}
impl Frame {
#[inline]
pub fn total_margin(&self) -> MarginF32 {
MarginF32::from(self.inner_margin)
+ MarginF32::from(self.stroke.width)
+ MarginF32::from(self.outer_margin)
}
pub fn fill_rect(&self, content_rect: Rect) -> Rect {
content_rect + self.inner_margin
}
pub fn widget_rect(&self, content_rect: Rect) -> Rect {
content_rect + self.inner_margin + MarginF32::from(self.stroke.width)
}
pub fn outer_rect(&self, content_rect: Rect) -> Rect {
content_rect + self.inner_margin + MarginF32::from(self.stroke.width) + self.outer_margin
}
}
pub struct Prepared {
pub frame: Frame,
where_to_put_background: ShapeIdx,
pub content_ui: Ui,
}
impl Frame {
pub fn begin(self, ui: &mut Ui) -> Prepared {
let where_to_put_background = ui.painter().add(Shape::Noop);
let outer_rect_bounds = ui.available_rect_before_wrap();
let mut max_content_rect = outer_rect_bounds - self.total_margin();
max_content_rect.max.x = max_content_rect.max.x.max(max_content_rect.min.x);
max_content_rect.max.y = max_content_rect.max.y.max(max_content_rect.min.y);
let content_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(self))
.max_rect(max_content_rect),
);
Prepared {
frame: self,
where_to_put_background,
content_ui,
}
}
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
self.show_dyn(ui, Box::new(add_contents))
}
pub fn show_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let mut prepared = self.begin(ui);
let ret = add_contents(&mut prepared.content_ui);
let response = prepared.end(ui);
InnerResponse::new(ret, response)
}
pub fn paint(&self, content_rect: Rect) -> Shape {
let Self {
inner_margin: _,
fill,
stroke,
corner_radius,
outer_margin: _,
shadow,
} = *self;
let widget_rect = self.widget_rect(content_rect);
let frame_shape = Shape::Rect(epaint::RectShape::new(
widget_rect,
corner_radius,
fill,
stroke,
epaint::StrokeKind::Inside,
));
if shadow == Default::default() {
frame_shape
} else {
let shadow = shadow.as_shape(widget_rect, corner_radius);
Shape::Vec(vec![Shape::from(shadow), frame_shape])
}
}
}
impl Prepared {
fn outer_rect(&self) -> Rect {
let content_rect = self.content_ui.min_rect();
content_rect
+ self.frame.inner_margin
+ MarginF32::from(self.frame.stroke.width)
+ self.frame.outer_margin
}
pub fn allocate_space(&self, ui: &mut Ui) -> Response {
ui.allocate_rect(self.outer_rect(), Sense::hover())
}
pub fn paint(&self, ui: &Ui) {
let content_rect = self.content_ui.min_rect();
let widget_rect = self.frame.widget_rect(content_rect);
if ui.is_rect_visible(widget_rect) {
let shape = self.frame.paint(content_rect);
ui.painter().set(self.where_to_put_background, shape);
}
}
pub fn end(self, ui: &mut Ui) -> Response {
self.paint(ui);
self.allocate_space(ui)
}
}