diff --git a/crates/egui/src/atomics/atom_widget.rs b/crates/egui/src/atomics/atom_widget.rs new file mode 100644 index 000000000..880e069fa --- /dev/null +++ b/crates/egui/src/atomics/atom_widget.rs @@ -0,0 +1,99 @@ +use crate::{ + Atom, AtomKind, AtomLayout, Button, Id, InnerResponse, Response, Sense, Ui, WidgetRect, +}; +use emath::Rect; + +pub fn atom() -> Atom<'static> { + Atom::default() +} + +pub trait AtomWidget<'a> { + fn atom_ui(self, ui: &mut Ui, response: &mut Response) -> AtomLayout<'a>; + + fn show_for(self, ui: &mut Ui) -> (AtomLayout<'a>, Response) + where + Self: Sized, + { + let id = ui.next_auto_id(); + ui.skip_ahead_auto_ids(1); + + let mut response = read_or_default_response(ui, id, Sense::hover()); + let mut layout = self.atom_ui(ui, &mut response); + layout = layout.id(id); + + (layout, response) + } +} + +#[macro_export] +macro_rules! impl_widget_for_atom_widget { + ($widget:ty) => { + impl Widget for $widget { + fn ui(self, ui: &mut Ui) -> Response { + let layout = self.show_for(ui).0; + ui.add(layout) + } + } + }; +} + +pub struct AtomUi<'ui, 'layout> { + ui: &'ui mut Ui, + layout: AtomLayout<'layout>, +} + +impl<'ui, 'layout> AtomUi<'ui, 'layout> { + pub fn new(ui: &'ui mut Ui) -> Self { + let layout = AtomLayout::new(()); + Self { ui, layout } + } + + pub fn add(&mut self, mut config: Atom<'layout>, widget: impl AtomWidget<'layout>) -> Response { + let (layout, response) = widget.show_for(self.ui); + + config.kind = AtomKind::Layout(Box::new(layout)); + + self.layout.push_right(config); + + response + } +} + +impl Ui { + pub fn atom_builder( + &mut self, + layout: AtomLayout, + add_contents: impl FnOnce(&mut AtomUi) -> T, + ) -> InnerResponse { + let mut ui = AtomUi { ui: self, layout }; + let inner = add_contents(&mut ui); + let AtomUi { ui, layout } = ui; + InnerResponse { + inner, + response: self.add(layout), + } + } + + pub fn atom(&mut self, add_contents: impl FnOnce(&mut AtomUi) -> T) -> InnerResponse { + self.atom_builder(AtomLayout::default(), add_contents) + } +} + +/// Read this widget's [`Response`] from a previous frame for state-based styling, or synthesize a +/// default (inactive) one if it hasn't been registered yet (e.g. the first frame). +/// +/// Mirrors the old `read_response(id).map(..).unwrap_or_default()` pattern, but yields a real +/// [`Response`] so it can feed [`Button::into_atom_ui`]. +fn read_or_default_response(ui: &Ui, id: Id, sense: Sense) -> Response { + ui.ctx().read_response(id).unwrap_or_else(|| { + ui.ctx().get_response(WidgetRect { + id, + parent_id: ui.id(), + layer_id: ui.layer_id(), + rect: Rect::NOTHING, + interact_rect: Rect::NOTHING, + sense, + enabled: ui.is_enabled(), + }) + }) +} diff --git a/crates/egui/src/atomics/mod.rs b/crates/egui/src/atomics/mod.rs index 7c8922c97..aa8919702 100644 --- a/crates/egui/src/atomics/mod.rs +++ b/crates/egui/src/atomics/mod.rs @@ -5,6 +5,7 @@ mod atom_layout; mod atoms; mod sized_atom; mod sized_atom_kind; +mod atom_widget; pub use atom::*; pub use atom_ext::*; @@ -13,3 +14,4 @@ pub use atom_layout::*; pub use atoms::*; pub use sized_atom::*; pub use sized_atom_kind::*; +pub use atom_widget::*; diff --git a/crates/egui/src/widgets/button.rs b/crates/egui/src/widgets/button.rs index a1526c50e..95107729f 100644 --- a/crates/egui/src/widgets/button.rs +++ b/crates/egui/src/widgets/button.rs @@ -1,11 +1,6 @@ use epaint::Margin; -use crate::{ - Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame, - Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, - Widget, WidgetInfo, WidgetText, WidgetType, - widget_style::{ButtonStyle, Classes, HasClasses, SELECTED_CLASS, WidgetState}, -}; +use crate::{Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Color32, CornerRadius, Frame, Id, Image, IntoAtoms, NumExt as _, Rect, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetRect, WidgetText, WidgetType, widget_style::{ButtonStyle, Classes, HasClasses, SELECTED_CLASS, WidgetState}, AtomWidget, impl_widget_for_atom_widget}; /// Clickable button with text. /// @@ -273,9 +268,10 @@ impl<'a> Button<'a> { self.layout = self.layout.gap(gap); self } +} - /// Show the button and return a [`AtomLayoutResponse`] for painting custom contents. - pub fn atom_ui(self, ui: &mut Ui) -> AtomLayoutResponse { +impl<'a> AtomWidget<'a> for Button<'a> { + fn atom_ui(self, ui: &mut Ui, response: &mut Response) -> AtomLayout<'a> { let Button { mut layout, fill, @@ -310,9 +306,7 @@ impl<'a> Button<'a> { let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame); - let id = ui.next_auto_id(); - let response: Option = ui.ctx().read_response(id); - let state = response.map(|r| r.widget_state()).unwrap_or_default(); + let state = response.widget_state(); classes.add_class_if(SELECTED_CLASS, selected); @@ -348,6 +342,10 @@ impl<'a> Button<'a> { .fallback_font(text_style.font_id.clone()) .fallback_text_color(text_style.color); + if image_tint_follows_text_color { + layout.map_images(|image| image.tint(text_style.color)); + } + // Retrocompatibility with button settings layout = if has_frame_margin && (state != WidgetState::Inactive || frame_when_inactive) { layout.frame(frame) @@ -355,28 +353,18 @@ impl<'a> Button<'a> { layout.frame(Frame::new().inner_margin(frame.inner_margin)) }; - let mut prepared = layout.min_size(min_size).allocate(ui); + layout = layout.min_size(min_size); - // Get AtomLayoutResponse, empty if not visible - let response = if ui.is_rect_visible(prepared.response.rect) { - if image_tint_follows_text_color { - prepared.map_images(|image| image.tint(text_style.color)); - } - prepared.fallback_text_color = text_style.color; - prepared.paint(ui) - } else { - AtomLayoutResponse::empty(prepared.response) - }; if let Some(cursor) = ui.visuals().interact_cursor - && response.response.hovered() + && response.hovered() { ui.ctx().set_cursor_icon(cursor); } - response.response.widget_info(|| { + response.widget_info(|| { if let Some(text) = &text { WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), text) } else { @@ -384,15 +372,11 @@ impl<'a> Button<'a> { } }); - response + layout } } -impl Widget for Button<'_> { - fn ui(self, ui: &mut Ui) -> Response { - self.atom_ui(ui).response - } -} +impl_widget_for_atom_widget!(Button<'_>); impl HasClasses for Button<'_> { fn classes(&self) -> &Classes {