mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 14:49:06 -04:00
Add AtomWidget
This commit is contained in:
99
crates/egui/src/atomics/atom_widget.rs
Normal file
99
crates/egui/src/atomics/atom_widget.rs
Normal 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(),
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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<Response> = 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 {
|
||||
|
||||
Reference in New Issue
Block a user