docking: paint panel icons in code with DockContent::paint_icon

This commit is contained in:
aiongg committed 2026-10-19 13:04:52 +00:00
1 parent f4f9e5b206
commit d91e5f6aea
4 files changed
+63 -7

No files matched your search

+43
View File
@@ -324,3 +324,46 @@ fn hit_rects_cover_every_gesture_source() {
assert!(opened || !out.actions.is_empty(), "{hit:?} at {rect:?} does nothing");
}
}
/// Paints panel 1's icon as a dot in code; panel 2 has neither a painted icon nor an image.
struct Painted;
impl DockContent<u32> for Painted {
fn body(&mut self, _: &mut egui::Ui, _: &u32) {}
fn logical_label(&self, panel: &u32) -> String {
format!("Panel {panel}")
}
fn paint_icon(&mut self, painter: &egui::Painter, rect: Rect, panel: &u32, color: egui::Color32) -> bool {
if *panel != 1 {
return false;
}
painter.circle_filled(rect.center(), 3.0, color);
true
}
}
#[test]
fn an_icon_painted_in_code_replaces_the_initial_and_others_fall_back() {
let d = DockDriver::new(docked(&[&[1, 2]], true), Painted);
let (one, two) = (d.hit(&Hit::Icon(1)).unwrap(), d.hit(&Hit::Icon(2)).unwrap());
let dots: Vec<Pos2> = d
.shapes()
.iter()
.filter_map(|clipped| match &clipped.shape {
egui::Shape::Circle(circle) if circle.radius == 3.0 => Some(circle.center),
_ => None,
})
.collect();
assert_eq!(dots.len(), 1, "{dots:?}");
assert!(one.contains(dots[0]) && !two.contains(dots[0]), "the dot sits in panel 1's cell: {dots:?}");
let initials: Vec<Pos2> = d
.shapes()
.iter()
.filter_map(|clipped| match &clipped.shape {
egui::Shape::Text(text) if text.galley.text() == "P" => Some(text.pos),
_ => None,
})
.collect();
assert_eq!(initials.len(), 1, "only panel 2 draws its initial: {initials:?}");
assert!(two.contains(initials[0]));
}
+8 -6
View File
@@ -230,12 +230,14 @@ impl<P: Clone + Eq + Hash + Debug + Send + Sync + 'static> Renderer<'_, P> {
let inset = ((self.style.iconic_width - size) * 0.5).max(2.0);
let icon = Rect::from_min_size(pos2(cell.left() + inset, cell.center().y - size * 0.5), vec2(size, size));
let color = if current { self.style.text } else { self.style.inactive_text };
if let Some(source) = self.content.icon(panel) {
egui::Image::new(source).tint(color).paint_at(ui, icon.intersect(cell));
} else {
let initial: String = label.chars().next().into_iter().flat_map(char::to_uppercase).collect();
painter.rect_stroke(icon, 3.0, Stroke::new(1.0, color), StrokeKind::Inside);
painter.text(icon.center(), egui::Align2::CENTER_CENTER, initial, self.style.font.clone(), color);
if !self.content.paint_icon(&painter, icon, panel, color) {
if let Some(source) = self.content.icon(panel) {
egui::Image::new(source).tint(color).paint_at(ui, icon.intersect(cell));
} else {
let initial: String = label.chars().next().into_iter().flat_map(char::to_uppercase).collect();
painter.rect_stroke(icon, 3.0, Stroke::new(1.0, color), StrokeKind::Inside);
painter.text(icon.center(), egui::Align2::CENTER_CENTER, initial, self.style.font.clone(), color);
}
}
if labels {
let text = self.content.label(ui, panel, &label, &self.style.font, color);
+10
View File
@@ -371,9 +371,16 @@ pub trait DockContent<P> {
fn floating_size(&self, _panel: &P) -> Option<Vec2> {
None
}
/// An image for the panel's icon, tinted with the icon color. [`Self::paint_icon`] goes first.
fn icon(&self, _panel: &P) -> Option<egui::ImageSource<'static>> {
None
}
/// Paint the panel's icon in code into `rect` with `color`, and return true. False draws
/// [`Self::icon`], or the label's initial when that is `None`. `painter` is clipped to the
/// icon's cell.
fn paint_icon(&mut self, _painter: &egui::Painter, _rect: Rect, _panel: &P, _color: Color32) -> bool {
false
}
/// Whether the panel may move to a native OS window (feature `native-windows`).
fn allow_native(&self, _panel: &P) -> bool {
false
@@ -455,6 +462,9 @@ impl<P, C: DockContent<P> + ?Sized> DockContent<P> for Overrides<'_, P, C> {
fn icon(&self, panel: &P) -> Option<egui::ImageSource<'static>> {
self.inner.icon(panel)
}
fn paint_icon(&mut self, painter: &egui::Painter, rect: Rect, panel: &P, color: Color32) -> bool {
self.inner.paint_icon(painter, rect, panel, color)
}
fn allow_native(&self, panel: &P) -> bool {
self.inner.allow_native(panel)
}
+2 -1
View File
@@ -104,7 +104,8 @@ its own command path, so pointer, keyboard and control commands share one path.
`DockContent<P>` is the app's adapter. It draws each panel body and gives its label, icon,
permissions, size limits, preferred floating size, panel menu and group header. `bare` hides the
tab strip of a lone panel. Panel bodies keep a stable egui id when they move, so widget memory
tab strip of a lone panel. An icon is an image (`icon`), or drawn in code by `paint_icon`, which
gets a painter, the icon rect and the tint color and returns true when it painted. Panel bodies keep a stable egui id when they move, so widget memory
survives.
`DockOutput` also has: