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

Add AtomWidgetContext and immediate_scope Ui interop

This commit is contained in:
lucasmerlin
2026-06-03 17:45:03 +02:00
parent 4167dbf48c
commit 28fd51391f
2 changed files with 214 additions and 23 deletions

View File

@@ -1,23 +1,21 @@
use crate::{ use crate::{Atom, AtomExt, AtomKind, AtomLayout, Atoms, Button, Color32, Context, Id, InnerResponse, IntoAtoms, Layout, Response, Sense, Spacing, Style, Ui, UiBuilder, Visuals, Widget, WidgetRect};
Atom, AtomKind, AtomLayout, Button, Id, InnerResponse, Response, Sense, Ui, WidgetRect, use emath::{Align, Pos2, Rect, Vec2};
}; use epaint::Direction;
use emath::Rect;
pub fn atom() -> Atom<'static> { pub fn atom() -> Atom<'static> {
Atom::default() Atom::default()
} }
pub trait AtomWidget<'a> { pub trait AtomWidget<'a> {
fn atom_ui(self, ui: &mut Ui, response: &mut Response) -> AtomLayout<'a>; fn atom_ui(self, ui: &mut AtomWidgetContext, response: &mut Response) -> AtomLayout<'a>;
fn show_for(self, ui: &mut Ui) -> (AtomLayout<'a>, Response) fn show_for(self, ui: &mut AtomWidgetContext) -> (AtomLayout<'a>, Response)
where where
Self: Sized, Self: Sized,
{ {
let id = ui.next_auto_id(); let id = ui.make_auto_id();
ui.skip_ahead_auto_ids(1); let mut response = ui.read_response(id);
let mut response = read_or_default_response(ui, id, Sense::hover());
let mut layout = self.atom_ui(ui, &mut response); let mut layout = self.atom_ui(ui, &mut response);
layout = layout.id(id); layout = layout.id(id);
@@ -25,11 +23,33 @@ pub trait AtomWidget<'a> {
} }
} }
impl<'a> AtomWidget<'a> for AtomLayout<'a> {
fn atom_ui(self, ui: &mut AtomWidgetContext, response: &mut Response) -> AtomLayout<'a> {
self
}
}
impl<'a> AtomWidget<'a> for Atom<'a> {
fn atom_ui(self, ui: &mut AtomWidgetContext, response: &mut Response) -> AtomLayout<'a> {
AtomLayout::new(self)
}
}
impl<'a> AtomWidget<'a> for AtomKind<'a> {
fn atom_ui(self, ui: &mut AtomWidgetContext, response: &mut Response) -> AtomLayout<'a> {
AtomLayout::new(self)
}
}
impl<'a> AtomWidget<'a> for Atoms<'a> {
fn atom_ui(self, ui: &mut AtomWidgetContext, response: &mut Response) -> AtomLayout<'a> {
AtomLayout::new(self)
}
}
#[macro_export] #[macro_export]
macro_rules! impl_widget_for_atom_widget { macro_rules! impl_widget_for_atom_widget {
($widget:ty) => { ($widget:ty) => {
impl Widget for $widget { impl $crate::Widget for $widget {
fn ui(self, ui: &mut Ui) -> Response { fn ui(self, ui: &mut $crate::Ui) -> $crate::Response {
let layout = self.show_for(ui).0; let layout = self.show_for(ui).0;
ui.add(layout) ui.add(layout)
} }
@@ -37,37 +57,206 @@ macro_rules! impl_widget_for_atom_widget {
}; };
} }
pub trait IsAtomWidgetContext {
fn ctx(&self) -> &crate::Context;
fn make_auto_id(&mut self) -> Id;
fn is_enabled(&self) -> bool;
fn style(&self) -> &Style;
fn style_mut(&mut self) -> &mut Style;
fn spacing(&self) -> &Spacing {
&self.style().spacing
}
fn spacing_mut(&mut self) -> &mut Spacing {
&mut self.style_mut().spacing
}
fn visuals(&self) -> &Visuals {
&self.style().visuals
}
fn visuals_mut(&mut self) -> &mut Visuals {
&mut self.style_mut().visuals
}
fn read_response(&self, id: Id) -> Response;
fn child_ui(&mut self, builder: UiBuilder) -> Ui;
}
pub type AtomWidgetContext = dyn IsAtomWidgetContext;
impl IsAtomWidgetContext for Ui {
fn ctx(&self) -> &Context {
self.ctx()
}
fn make_auto_id(&mut self) -> Id {
let id = self.next_auto_id();
self.skip_ahead_auto_ids(1);
id
}
fn is_enabled(&self) -> bool {
self.is_enabled()
}
fn style(&self) -> &Style {
self.style()
}
fn style_mut(&mut self) -> &mut Style {
self.style_mut()
}
fn read_response(&self, id: Id) -> Response {
read_or_default_response(&self, id, Sense::hover())
}
fn child_ui(&mut self, builder: UiBuilder) -> Ui {
Ui::new_child(self, builder)
}
}
pub struct AtomUi<'ui, 'layout> { pub struct AtomUi<'ui, 'layout> {
ui: &'ui mut Ui, ctx: &'ui mut AtomWidgetContext,
layout: AtomLayout<'layout>, layout: AtomLayout<'layout>,
} }
impl<'ui, 'layout> AtomUi<'ui, 'layout> { impl<'ui, 'layout> AtomUi<'ui, 'layout> {
pub fn new(ui: &'ui mut Ui) -> Self { pub fn new(ctx: &'ui mut AtomWidgetContext, builder: AtomLayout<'layout>) -> Self {
let layout = AtomLayout::new(()); let layout = builder.id(ctx.make_auto_id());
Self { ui, layout } Self { ctx, layout }
}
pub fn response(&self) -> Response {
self.ctx
.read_response(self.layout.id.expect("set in constructor"))
} }
pub fn add(&mut self, mut config: Atom<'layout>, widget: impl AtomWidget<'layout>) -> Response { pub fn add(&mut self, mut config: Atom<'layout>, widget: impl AtomWidget<'layout>) -> Response {
let (layout, response) = widget.show_for(self.ui); let (layout, response) = widget.show_for(self.ctx);
config.kind = AtomKind::Layout(Box::new(layout)); config.kind = AtomKind::Layout(Box::new(layout));
self.layout.push_right(config); self.layout.push_right(config);
response response
} }
pub fn scope_builder<R>(
&mut self,
builder: AtomLayout<'layout>,
mut atom: Atom<'layout>,
add_content: impl FnOnce(&mut AtomUi) -> R,
) -> InnerResponse<R> {
let mut child = AtomUi::new(self.ctx, builder);
let inner = add_content(&mut child);
let response = InnerResponse {
inner,
response: child.response(),
};
atom.kind = AtomKind::Layout(Box::new(child.layout));
self.layout.push_right(atom);
response
}
pub fn vertical<R>(
&mut self,
atom: Atom<'layout>,
add_content: impl FnOnce(&mut AtomUi) -> R,
) -> InnerResponse<R> {
self.scope_builder(
AtomLayout::default().direction(Direction::TopDown),
atom,
add_content,
)
}
pub fn show(self) -> (AtomLayout<'layout>, Response) {
let response = self.response();
(self.layout, response)
}
pub fn immediate_scope<R>(
&mut self,
mut ui_builder: UiBuilder,
mut atom: Atom<'layout>,
add_content: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let sizing_id = self.ctx.make_auto_id();
let mut sizing_response = self.ctx.read_response(sizing_id);
let mut size = Vec2::ZERO;
if sizing_response.rect.is_finite() && sizing_response.rect.is_positive() {
size = sizing_response
.intrinsic_size()
.unwrap_or(sizing_response.rect.size());
}
let placement_response = self.add(atom.clone(), AtomLayout::new(atom.atom_size(size)));
if placement_response.rect.is_finite() && placement_response.rect.is_positive() {
ui_builder = ui_builder.max_rect(Rect::from_min_size(
placement_response.rect.min,
Vec2::INFINITY,
));
} else {
ui_builder = ui_builder
.max_rect(Rect::from_min_size(Pos2::ZERO, Vec2::INFINITY))
.invisible();
}
ui_builder = ui_builder
.id(sizing_id)
.layout(Layout::left_to_right(Align::Min));
let mut immediate_ui = self.ctx.child_ui(ui_builder);
let inner = add_content(&mut immediate_ui);
InnerResponse {
inner,
response: sizing_response,
}
}
/// Show a label which can be selected or not.
///
/// See also [`Button::selectable`] and [`Self::toggle_value`].
#[must_use = "You should check if the user clicked this with `if ui.selectable_label(…).clicked() { … } "]
pub fn selectable_label(&mut self, checked: bool, text: impl IntoAtoms<'layout>) -> Response {
self.add(atom().atom_grow(true), Button::selectable(checked, text))
}
/// Show selectable text. It is selected if `*current_value == selected_value`.
/// If clicked, `selected_value` is assigned to `*current_value`.
///
/// Example: `ui.selectable_value(&mut my_enum, Enum::Alternative, "Alternative")`.
///
/// See also [`Button::selectable`] and [`Self::toggle_value`].
pub fn selectable_value<Value: PartialEq>(
&mut self,
current_value: &mut Value,
selected_value: Value,
text: impl IntoAtoms<'layout>,
) -> Response {
let mut response = self.selectable_label(*current_value == selected_value, text);
if response.clicked() && *current_value != selected_value {
*current_value = selected_value;
response.mark_changed();
}
response
}
} }
impl Ui { impl Ui {
pub fn atom_builder<T>( pub fn atom_builder<T>(
&mut self, &mut self,
layout: AtomLayout, builder: AtomLayout,
add_contents: impl FnOnce(&mut AtomUi) -> T, add_contents: impl FnOnce(&mut AtomUi) -> T,
) -> InnerResponse<T> { ) -> InnerResponse<T> {
let mut ui = AtomUi { ui: self, layout }; let mut ui = AtomUi::new(self, builder);
let inner = add_contents(&mut ui); let inner = add_contents(&mut ui);
let AtomUi { ui, layout } = ui; let AtomUi { ctx, layout } = ui;
InnerResponse { InnerResponse {
inner, inner,
response: self.add(layout), response: self.add(layout),

View File

@@ -1,6 +1,6 @@
use epaint::Margin; use epaint::Margin;
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}; 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, AtomWidgetContext};
/// Clickable button with text. /// Clickable button with text.
/// ///
@@ -271,7 +271,7 @@ impl<'a> Button<'a> {
} }
impl<'a> AtomWidget<'a> for Button<'a> { impl<'a> AtomWidget<'a> for Button<'a> {
fn atom_ui(self, ui: &mut Ui, response: &mut Response) -> AtomLayout<'a> { fn atom_ui(self, ui: &mut AtomWidgetContext, response: &mut Response) -> AtomLayout<'a> {
let Button { let Button {
mut layout, mut layout,
fill, fill,
@@ -295,7 +295,9 @@ impl<'a> AtomWidget<'a> for Button<'a> {
if limit_image_size { if limit_image_size {
layout.map_atoms(|atom| { layout.map_atoms(|atom| {
if matches!(&atom.kind, AtomKind::Image(_)) { if matches!(&atom.kind, AtomKind::Image(_)) {
atom.atom_max_height_font_size(ui) // TODO
// atom.atom_max_height_font_size(ui)
atom
} else { } else {
atom atom
} }