Improve `Frame` API to allow picking color until after adding content (#3889)
Previously the `Frame` API only supported picking colors once, and then
adding widgets.
But what if you want to add widgets first, and THEN pick the colors for
the frame? Now you can!
```rs
let frame = Frame::default().inner_margin(4.0).begin(ui);
{
frame.content_ui.label("Inside the frame");
frame.content_ui.label("This too");
}
let response = frame.allocate_space(ui);
if response.hovered() {
frame.frame.stroke = Stroke::new(2.0, Color32::WHITE);
}
frame.paint(ui);
```
This commit is contained in:
parent
a815923717
commit
6b0782c96b
|
|
@ -14,6 +14,43 @@ use epaint::*;
|
||||||
/// });
|
/// });
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Dynamic color
|
||||||
|
/// If you want to change the color of the frame based on the response of
|
||||||
|
/// the widget, you needs to break it up into multiple steps:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
/// let mut frame = egui::Frame::default().inner_margin(4.0).begin(ui);
|
||||||
|
/// {
|
||||||
|
/// let response = frame.content_ui.label("Inside the frame");
|
||||||
|
/// if response.hovered() {
|
||||||
|
/// frame.frame.fill = egui::Color32::RED;
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// frame.end(ui); // Will "close" the frame.
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// You can also respond to the hovering of the frame itself:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # egui::__run_test_ui(|ui| {
|
||||||
|
/// let mut frame = egui::Frame::default().inner_margin(4.0).begin(ui);
|
||||||
|
/// {
|
||||||
|
/// frame.content_ui.label("Inside the frame");
|
||||||
|
/// frame.content_ui.label("This too");
|
||||||
|
/// }
|
||||||
|
/// let response = frame.allocate_space(ui);
|
||||||
|
/// if response.hovered() {
|
||||||
|
/// frame.frame.fill = egui::Color32::RED;
|
||||||
|
/// }
|
||||||
|
/// frame.paint(ui);
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that you cannot change the margins after calling `begin`.
|
||||||
|
#[doc(alias = "border")]
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||||
#[must_use = "You should call .show()"]
|
#[must_use = "You should call .show()"]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
|
|
@ -178,12 +215,26 @@ impl Frame {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
pub struct Prepared {
|
pub struct Prepared {
|
||||||
|
/// The frame that was prepared.
|
||||||
|
///
|
||||||
|
/// The margin has already been read and used,
|
||||||
|
/// but the rest of the fields may be modified.
|
||||||
pub frame: Frame,
|
pub frame: Frame,
|
||||||
|
|
||||||
|
/// This is where we will insert the frame shape so it ends up behind the content.
|
||||||
where_to_put_background: ShapeIdx,
|
where_to_put_background: ShapeIdx,
|
||||||
|
|
||||||
|
/// Add your widgets to this UI so it ends up within the frame.
|
||||||
pub content_ui: Ui,
|
pub content_ui: Ui,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
impl Frame {
|
||||||
|
/// Begin a dynamically colored frame.
|
||||||
|
///
|
||||||
|
/// This is a more advanced API.
|
||||||
|
/// Usually you want to use [`Self::show`] instead.
|
||||||
|
///
|
||||||
|
/// See docs for [`Frame`] for an example.
|
||||||
pub fn begin(self, ui: &mut Ui) -> Prepared {
|
pub fn begin(self, ui: &mut Ui) -> Prepared {
|
||||||
let where_to_put_background = ui.painter().add(Shape::Noop);
|
let where_to_put_background = ui.painter().add(Shape::Noop);
|
||||||
let outer_rect_bounds = ui.available_rect_before_wrap();
|
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||||
|
|
@ -205,6 +256,7 @@ impl Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show the given ui surrounded by this frame.
|
||||||
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
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))
|
self.show_dyn(ui, Box::new(add_contents))
|
||||||
}
|
}
|
||||||
|
|
@ -220,6 +272,9 @@ impl Frame {
|
||||||
InnerResponse::new(ret, response)
|
InnerResponse::new(ret, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Paint this frame as a shape.
|
||||||
|
///
|
||||||
|
/// The margin is ignored.
|
||||||
pub fn paint(&self, outer_rect: Rect) -> Shape {
|
pub fn paint(&self, outer_rect: Rect) -> Shape {
|
||||||
let Self {
|
let Self {
|
||||||
inner_margin: _,
|
inner_margin: _,
|
||||||
|
|
@ -243,30 +298,37 @@ impl Frame {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepared {
|
impl Prepared {
|
||||||
fn paint_rect(&self) -> Rect {
|
|
||||||
self.frame
|
|
||||||
.inner_margin
|
|
||||||
.expand_rect(self.content_ui.min_rect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn content_with_margin(&self) -> Rect {
|
fn content_with_margin(&self) -> Rect {
|
||||||
(self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect())
|
(self.frame.inner_margin + self.frame.outer_margin).expand_rect(self.content_ui.min_rect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end(self, ui: &mut Ui) -> Response {
|
/// Allocate the the space that was used by [`Self::content_ui`].
|
||||||
let paint_rect = self.paint_rect();
|
///
|
||||||
|
/// This MUST be called, or the parent ui will not know how much space this widget used.
|
||||||
let Self {
|
///
|
||||||
frame,
|
/// This can be called before or after [`Self::paint`].
|
||||||
where_to_put_background,
|
pub fn allocate_space(&self, ui: &mut Ui) -> Response {
|
||||||
..
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
if ui.is_rect_visible(paint_rect) {
|
|
||||||
let shape = frame.paint(paint_rect);
|
|
||||||
ui.painter().set(where_to_put_background, shape);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui.allocate_rect(self.content_with_margin(), Sense::hover())
|
ui.allocate_rect(self.content_with_margin(), Sense::hover())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Paint the frame.
|
||||||
|
///
|
||||||
|
/// This can be called before or after [`Self::allocate_space`].
|
||||||
|
pub fn paint(&self, ui: &Ui) {
|
||||||
|
let paint_rect = self
|
||||||
|
.frame
|
||||||
|
.inner_margin
|
||||||
|
.expand_rect(self.content_ui.min_rect());
|
||||||
|
|
||||||
|
if ui.is_rect_visible(paint_rect) {
|
||||||
|
let shape = self.frame.paint(paint_rect);
|
||||||
|
ui.painter().set(self.where_to_put_background, shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience for calling [`Self::allocate_space`] and [`Self::paint`].
|
||||||
|
pub fn end(self, ui: &mut Ui) -> Response {
|
||||||
|
self.paint(ui);
|
||||||
|
self.allocate_space(ui)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue