1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-29 04:40:03 -04:00

Add AtomWidget

This commit is contained in:
lucasmerlin
2026-06-02 13:08:27 +02:00
committed by Lucas Meurer
parent d802a982ce
commit 1ad451eb28
4 changed files with 120 additions and 32 deletions

View File

@@ -82,11 +82,12 @@ impl<'a> Atom<'a> {
/// ///
/// Example: /// Example:
/// ``` /// ```
/// # use egui::{AtomExt, AtomKind, Atom, Button, Id, __run_test_ui}; /// # use egui::{AtomExt, AtomKind, Atom, AtomWidget, Button, Id, __run_test_ui};
/// # use emath::Vec2; /// # use emath::Vec2;
/// # __run_test_ui(|ui| { /// # __run_test_ui(|ui| {
/// let id = Id::new("my_button"); /// let id = Id::new("my_button");
/// let response = Button::new(("Hi!", Atom::custom(id, Vec2::splat(18.0)))).atom_ui(ui); /// let button = Button::new(("Hi!", Atom::custom(id, Vec2::splat(18.0))));
/// let response = button.show_for(ui).0.show(ui);
/// ///
/// let rect = response.rect(id); /// let rect = response.rect(id);
/// if let Some(rect) = rect { /// if let Some(rect) = rect {

View File

@@ -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<T>(
&mut self,
layout: AtomLayout,
add_contents: impl FnOnce(&mut AtomUi) -> T,
) -> InnerResponse<T> {
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<T>(&mut self, add_contents: impl FnOnce(&mut AtomUi) -> T) -> InnerResponse<T> {
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(),
})
})
}

View File

@@ -5,6 +5,7 @@ mod atom_layout;
mod atoms; mod atoms;
mod sized_atom; mod sized_atom;
mod sized_atom_kind; mod sized_atom_kind;
mod atom_widget;
pub use atom::*; pub use atom::*;
pub use atom_ext::*; pub use atom_ext::*;
@@ -13,3 +14,4 @@ pub use atom_layout::*;
pub use atoms::*; pub use atoms::*;
pub use sized_atom::*; pub use sized_atom::*;
pub use sized_atom_kind::*; pub use sized_atom_kind::*;
pub use atom_widget::*;

View File

@@ -1,9 +1,9 @@
use epaint::Margin; use epaint::Margin;
use crate::{ use crate::{
Atom, AtomExt as _, AtomKind, AtomLayout, AtomLayoutResponse, Atoms, Color32, CornerRadius, Atom, AtomExt as _, AtomKind, AtomLayout, AtomWidget, Atoms, Color32, CornerRadius, Frame,
Frame, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Image, IntoAtoms, NumExt as _, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2,
Vec2, Widget, WidgetInfo, WidgetText, WidgetType, Widget, WidgetInfo, WidgetText, WidgetType, impl_widget_for_atom_widget,
widget_style::{ButtonStyle, Classes, HasClasses, SELECTED_CLASS, WidgetState}, widget_style::{ButtonStyle, Classes, HasClasses, SELECTED_CLASS, WidgetState},
}; };
@@ -285,9 +285,10 @@ impl<'a> Button<'a> {
pub fn atoms(&self) -> &Atoms<'a> { pub fn atoms(&self) -> &Atoms<'a> {
&self.layout.atoms &self.layout.atoms
} }
}
/// Show the button and return a [`AtomLayoutResponse`] for painting custom contents. impl<'a> AtomWidget<'a> for Button<'a> {
pub fn atom_ui(self, ui: &mut Ui) -> AtomLayoutResponse { fn atom_ui(self, ui: &mut Ui, response: &mut Response) -> AtomLayout<'a> {
let Button { let Button {
mut layout, mut layout,
fill, fill,
@@ -322,9 +323,7 @@ impl<'a> Button<'a> {
let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame); let has_frame_margin = frame.unwrap_or_else(|| ui.visuals().button_frame);
let id = ui.next_auto_id(); let state = response.widget_state();
let response: Option<Response> = ui.ctx().read_response(id);
let state = response.map(|r| r.widget_state()).unwrap_or_default();
classes.add_class_if(SELECTED_CLASS, selected.unwrap_or(false)); classes.add_class_if(SELECTED_CLASS, selected.unwrap_or(false));
@@ -360,6 +359,10 @@ impl<'a> Button<'a> {
.fallback_font(text_style.font_id.clone()) .fallback_font(text_style.font_id.clone())
.fallback_text_color(text_style.color); .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 // Retrocompatibility with button settings
layout = if has_frame_margin && (state != WidgetState::Inactive || frame_when_inactive) { layout = if has_frame_margin && (state != WidgetState::Inactive || frame_when_inactive) {
layout.frame(frame) layout.frame(frame)
@@ -367,28 +370,15 @@ impl<'a> Button<'a> {
layout.frame(Frame::new().inner_margin(frame.inner_margin)) 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 if let Some(cursor) = ui.visuals().interact_cursor
&& response.response.hovered() && response.hovered()
{ {
ui.ctx().set_cursor_icon(cursor); ui.ctx().set_cursor_icon(cursor);
} }
response.response.widget_info(|| match (selected, &text) { response.widget_info(|| match (selected, &text) {
(Some(selected), Some(text)) => { (Some(selected), Some(text)) => {
WidgetInfo::selected(WidgetType::Button, ui.is_enabled(), selected, text) WidgetInfo::selected(WidgetType::Button, ui.is_enabled(), selected, text)
} }
@@ -402,15 +392,11 @@ impl<'a> Button<'a> {
(None, None) => WidgetInfo::new(WidgetType::Button), (None, None) => WidgetInfo::new(WidgetType::Button),
}); });
response layout
} }
} }
impl Widget for Button<'_> { impl_widget_for_atom_widget!(Button<'_>);
fn ui(self, ui: &mut Ui) -> Response {
self.atom_ui(ui).response
}
}
impl HasClasses for Button<'_> { impl HasClasses for Button<'_> {
fn classes(&self) -> &Classes { fn classes(&self) -> &Classes {