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:
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user