1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

⚠️ Frame now includes stroke width as part of padding (#5575)

* Part of https://github.com/emilk/egui/issues/4019

`Frame` now includes the width of the stroke as part of its size. From
the new docs:

### `Frame` docs
The total (outer) size of a frame is `content_size + inner_margin +
2*stroke.width + outer_margin`.

Everything within the stroke is filled with the fill color (if any).

```text
+-----------------^-------------------------------------- -+
|                 | outer_margin                           |
|    +------------v----^------------------------------+    |
|    |                 | stroke width                 |    |
|    |    +------------v---^---------------------+    |    |
|    |    |                | inner_margin        |    |    |
|    |    |    +-----------v----------------+    |    |    |
|    |    |    |             ^              |    |    |    |
|    |    |    |             |              |    |    |    |
|    |    |    |<------ content_size ------>|    |    |    |
|    |    |    |             |              |    |    |    |
|    |    |    |             v              |    |    |    |
|    |    |    +------- content_rect -------+    |    |    |
|    |    |                                      |    |    |
|    |    +-------------fill_rect ---------------+    |    |
|    |                                                |    |
|    +----------------- widget_rect ------------------+    |
|                                                          |
+---------------------- outer_rect ------------------------+
```

The four rectangles, from inside to outside, are:
* `content_rect`: the rectangle that is made available to the inner
[`Ui`] or widget.
* `fill_rect`: the rectangle that is filled with the fill color (inside
the stroke, if any).
* `widget_rect`: is the interactive part of the widget (what sense
clicks etc).
* `outer_rect`: what is allocated in the outer [`Ui`], and is what is
returned by [`Response::rect`].

### Notes
This required rewriting a lot of the layout code for `egui::Window`,
which was a massive pain. But now the window margin and stroke width is
properly accounted for everywhere.
This commit is contained in:
Emil Ernerfeldt
2025-01-04 10:29:22 +01:00
committed by GitHub
parent 938d8b0d2e
commit 6607cd95f9
42 changed files with 680 additions and 508 deletions

View File

@@ -62,27 +62,23 @@ impl eframe::App for MyApp {
// nested frames test
ui.add_space(20.0);
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(4),
outer_margin: egui::Margin::same(4),
..Default::default()
}
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(8),
outer_margin: egui::Margin::same(6),
..Default::default()
}
egui::Frame::new()
.stroke(ui.visuals().noninteractive().bg_stroke)
.inner_margin(4)
.outer_margin(4)
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
egui::Frame::new()
.stroke(ui.visuals().noninteractive().bg_stroke)
.inner_margin(8)
.outer_margin(6)
.show(ui, |ui| {
full_span_widget(ui, false);
stack_ui(ui);
});
});
});
});
});
@@ -126,18 +122,16 @@ impl eframe::App for MyApp {
// Ui nesting test
ui.add_space(20.0);
ui.label("UI nesting test:");
egui::Frame {
stroke: ui.visuals().noninteractive().bg_stroke,
inner_margin: egui::Margin::same(4),
..Default::default()
}
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.scope(stack_ui);
egui::Frame::new()
.stroke(ui.visuals().noninteractive().bg_stroke)
.inner_margin(4)
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.scope(stack_ui);
});
});
});
});
// table test
let mut cell_stack = None;
@@ -265,106 +259,104 @@ fn stack_ui(ui: &mut egui::Ui) {
}
fn stack_ui_impl(ui: &mut egui::Ui, stack: &egui::UiStack) {
egui::Frame {
stroke: ui.style().noninteractive().fg_stroke,
inner_margin: egui::Margin::same(4),
..Default::default()
}
.show(ui, |ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
egui::Frame::new()
.stroke(ui.style().noninteractive().fg_stroke)
.inner_margin(4)
.show(ui, |ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
egui_extras::TableBuilder::new(ui)
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("id");
});
header.col(|ui| {
ui.strong("kind");
});
header.col(|ui| {
ui.strong("stroke");
});
header.col(|ui| {
ui.strong("inner");
});
header.col(|ui| {
ui.strong("outer");
});
header.col(|ui| {
ui.strong("direction");
});
})
.body(|mut body| {
for node in stack.iter() {
body.row(20.0, |mut row| {
row.col(|ui| {
if ui.label(format!("{:?}", node.id)).hovered() {
ui.ctx().debug_painter().debug_rect(
node.max_rect,
egui::Color32::GREEN,
"max",
);
ui.ctx().debug_painter().circle_filled(
node.min_rect.min,
2.0,
egui::Color32::RED,
);
}
});
row.col(|ui| {
let s = if let Some(kind) = node.kind() {
format!("{kind:?}")
} else {
"-".to_owned()
};
ui.label(s);
});
row.col(|ui| {
let frame = node.frame();
if frame.stroke == egui::Stroke::NONE {
ui.label("-");
} else {
let mut layout_job = egui::text::LayoutJob::default();
layout_job.append(
"",
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
frame.stroke.color,
),
);
layout_job.append(
format!("{}px", frame.stroke.width).as_str(),
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
ui.style().visuals.text_color(),
),
);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.label(layout_job);
}
});
row.col(|ui| {
ui.label(print_margin(&node.frame().inner_margin));
});
row.col(|ui| {
ui.label(print_margin(&node.frame().outer_margin));
});
row.col(|ui| {
ui.label(format!("{:?}", node.layout_direction));
});
egui_extras::TableBuilder::new(ui)
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("id");
});
}
});
});
header.col(|ui| {
ui.strong("kind");
});
header.col(|ui| {
ui.strong("stroke");
});
header.col(|ui| {
ui.strong("inner");
});
header.col(|ui| {
ui.strong("outer");
});
header.col(|ui| {
ui.strong("direction");
});
})
.body(|mut body| {
for node in stack.iter() {
body.row(20.0, |mut row| {
row.col(|ui| {
if ui.label(format!("{:?}", node.id)).hovered() {
ui.ctx().debug_painter().debug_rect(
node.max_rect,
egui::Color32::GREEN,
"max",
);
ui.ctx().debug_painter().circle_filled(
node.min_rect.min,
2.0,
egui::Color32::RED,
);
}
});
row.col(|ui| {
let s = if let Some(kind) = node.kind() {
format!("{kind:?}")
} else {
"-".to_owned()
};
ui.label(s);
});
row.col(|ui| {
let frame = node.frame();
if frame.stroke == egui::Stroke::NONE {
ui.label("-");
} else {
let mut layout_job = egui::text::LayoutJob::default();
layout_job.append(
"",
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
frame.stroke.color,
),
);
layout_job.append(
format!("{}px", frame.stroke.width).as_str(),
0.0,
egui::TextFormat::simple(
egui::TextStyle::Body.resolve(ui.style()),
ui.style().visuals.text_color(),
),
);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.label(layout_job);
}
});
row.col(|ui| {
ui.label(print_margin(&node.frame().inner_margin));
});
row.col(|ui| {
ui.label(print_margin(&node.frame().outer_margin));
});
row.col(|ui| {
ui.label(format!("{:?}", node.layout_direction));
});
});
}
});
});
}
fn print_margin(margin: &egui::Margin) -> String {