mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 07:03:14 -04:00
Add Atomics, IntoAtomics, Atomic, AtomicKind, implement Button with new Atomics
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
use crate::style::StyleModifier;
|
||||
use crate::{
|
||||
Button, Color32, Context, Frame, Id, InnerResponse, Layout, Popup, PopupCloseBehavior,
|
||||
Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget, WidgetText,
|
||||
Button, Color32, Context, Frame, Id, InnerResponse, IntoAtomics, Layout, Popup,
|
||||
PopupCloseBehavior, Response, Style, Ui, UiBuilder, UiKind, UiStack, UiStackInfo, Widget,
|
||||
WidgetText,
|
||||
};
|
||||
use emath::{vec2, Align, RectAlign, Vec2};
|
||||
use epaint::Stroke;
|
||||
@@ -242,8 +243,8 @@ pub struct MenuButton<'a> {
|
||||
}
|
||||
|
||||
impl<'a> MenuButton<'a> {
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self::from_button(Button::new(text))
|
||||
pub fn new(text: impl IntoAtomics<'a>) -> Self {
|
||||
Self::from_button(Button::new(text.into_atomics()))
|
||||
}
|
||||
|
||||
/// Set the config for the menu.
|
||||
@@ -292,8 +293,8 @@ impl<'a> SubMenuButton<'a> {
|
||||
/// The default right arrow symbol: `"⏵"`
|
||||
pub const RIGHT_ARROW: &'static str = "⏵";
|
||||
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self::from_button(Button::new(text).right_text("⏵"))
|
||||
pub fn new(text: impl IntoAtomics<'a>) -> Self {
|
||||
Self::from_button(Button::new(text.into_atomics()).right_text("⏵"))
|
||||
}
|
||||
|
||||
/// Create a new submenu button from a [`Button`].
|
||||
|
||||
@@ -1514,7 +1514,7 @@ impl Widgets {
|
||||
inactive: WidgetVisuals {
|
||||
weak_bg_fill: Color32::from_gray(230), // button background
|
||||
bg_fill: Color32::from_gray(230), // checkbox background
|
||||
bg_stroke: Default::default(),
|
||||
bg_stroke: Stroke::new(1.0, Color32::default()),
|
||||
fg_stroke: Stroke::new(1.0, Color32::from_gray(60)), // button text
|
||||
corner_radius: CornerRadius::same(2),
|
||||
expansion: 0.0,
|
||||
|
||||
@@ -3,65 +3,44 @@ use emath::{Align2, Vec2};
|
||||
use epaint::Galley;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Naming: AtimicItem
|
||||
enum WidgetLayoutItemType<'a> {
|
||||
Text(WidgetText),
|
||||
Image(Image<'a>),
|
||||
Custom(Vec2),
|
||||
Grow,
|
||||
}
|
||||
|
||||
enum SizedWidgetLayoutItemType<'a> {
|
||||
pub enum SizedAtomicKind<'a> {
|
||||
Text(Arc<Galley>),
|
||||
Image(Image<'a>, Vec2),
|
||||
Custom(Vec2),
|
||||
Grow,
|
||||
}
|
||||
|
||||
struct Item {
|
||||
align2: Align2,
|
||||
}
|
||||
|
||||
impl Default for Item {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
align2: Align2::LEFT_CENTER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SizedWidgetLayoutItemType<'_> {
|
||||
impl SizedAtomicKind<'_> {
|
||||
pub fn size(&self) -> Vec2 {
|
||||
match self {
|
||||
SizedWidgetLayoutItemType::Text(galley) => galley.size(),
|
||||
SizedWidgetLayoutItemType::Image(_, size) => *size,
|
||||
SizedWidgetLayoutItemType::Custom(size) => *size,
|
||||
SizedWidgetLayoutItemType::Grow => Vec2::ZERO,
|
||||
SizedAtomicKind::Text(galley) => galley.size(),
|
||||
SizedAtomicKind::Image(_, size) => *size,
|
||||
SizedAtomicKind::Custom(size) => *size,
|
||||
SizedAtomicKind::Grow => Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// AtomicLayout
|
||||
struct WidgetLayout<'a> {
|
||||
/// TODO: SmallVec?
|
||||
items: Vec<(Item, WidgetLayoutItemType<'a>)>,
|
||||
pub struct WidgetLayout<'a> {
|
||||
pub atomics: Atomics<'a>,
|
||||
gap: f32,
|
||||
frame: Frame,
|
||||
sense: Sense,
|
||||
pub(crate) frame: Frame,
|
||||
pub(crate) sense: Sense,
|
||||
}
|
||||
|
||||
impl<'a> WidgetLayout<'a> {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(atomics: impl IntoAtomics<'a>) -> Self {
|
||||
Self {
|
||||
items: Vec::new(),
|
||||
atomics: atomics.into_atomics(),
|
||||
gap: 4.0,
|
||||
frame: Frame::default(),
|
||||
sense: Sense::hover(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(mut self, item: Item, kind: impl Into<WidgetLayoutItemType<'a>>) -> Self {
|
||||
self.items.push((item, kind.into()));
|
||||
pub fn add(mut self, atomic: impl Into<Atomic<'a>>) -> Self {
|
||||
self.atomics.add(atomic.into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -93,27 +72,25 @@ impl<'a> WidgetLayout<'a> {
|
||||
|
||||
let mut grow_count = 0;
|
||||
|
||||
for (item, kind) in self.items {
|
||||
let (preferred_size, sized) = match kind {
|
||||
WidgetLayoutItemType::Text(text) => {
|
||||
for (item) in self.atomics.0 {
|
||||
let (preferred_size, sized) = match item.kind {
|
||||
AtomicKind::Text(text) => {
|
||||
let galley = text.into_galley(ui, None, available_width, TextStyle::Button);
|
||||
(
|
||||
galley.size(), // TODO
|
||||
SizedWidgetLayoutItemType::Text(galley),
|
||||
SizedAtomicKind::Text(galley),
|
||||
)
|
||||
}
|
||||
WidgetLayoutItemType::Image(image) => {
|
||||
AtomicKind::Image(image) => {
|
||||
let size =
|
||||
image.load_and_calc_size(ui, Vec2::min(available_size, Vec2::splat(16.0)));
|
||||
let size = size.unwrap_or_default();
|
||||
(size, SizedWidgetLayoutItemType::Image(image, size))
|
||||
(size, SizedAtomicKind::Image(image, size))
|
||||
}
|
||||
WidgetLayoutItemType::Custom(size) => {
|
||||
(size, SizedWidgetLayoutItemType::Custom(size))
|
||||
}
|
||||
WidgetLayoutItemType::Grow => {
|
||||
AtomicKind::Custom(size) => (size, SizedAtomicKind::Custom(size)),
|
||||
AtomicKind::Grow => {
|
||||
grow_count += 1;
|
||||
(Vec2::ZERO, SizedWidgetLayoutItemType::Grow)
|
||||
(Vec2::ZERO, SizedAtomicKind::Grow)
|
||||
}
|
||||
};
|
||||
let size = sized.size();
|
||||
@@ -123,7 +100,7 @@ impl<'a> WidgetLayout<'a> {
|
||||
|
||||
height = height.max(size.y);
|
||||
|
||||
sized_items.push((item, sized));
|
||||
sized_items.push(sized);
|
||||
}
|
||||
|
||||
if sized_items.len() > 1 {
|
||||
@@ -147,28 +124,29 @@ impl<'a> WidgetLayout<'a> {
|
||||
|
||||
let mut cursor = content_rect.left();
|
||||
|
||||
for (item, sized) in sized_items {
|
||||
for sized in sized_items {
|
||||
let size = sized.size();
|
||||
let width = match sized {
|
||||
SizedWidgetLayoutItemType::Grow => grow_width,
|
||||
SizedAtomicKind::Grow => grow_width,
|
||||
_ => size.x,
|
||||
};
|
||||
|
||||
let frame = content_rect.with_min_x(cursor).with_max_x(cursor + width);
|
||||
cursor = frame.right() + self.gap;
|
||||
|
||||
let rect = item.align2.align_size_within_rect(size, frame);
|
||||
let align = Align2::CENTER_CENTER;
|
||||
let rect = align.align_size_within_rect(size, frame);
|
||||
|
||||
match sized {
|
||||
SizedWidgetLayoutItemType::Text(galley) => {
|
||||
SizedAtomicKind::Text(galley) => {
|
||||
ui.painter()
|
||||
.galley(rect.min, galley, ui.visuals().text_color());
|
||||
}
|
||||
SizedWidgetLayoutItemType::Image(image, _) => {
|
||||
SizedAtomicKind::Image(image, _) => {
|
||||
image.paint_at(ui, rect);
|
||||
}
|
||||
SizedWidgetLayoutItemType::Custom(_) => {}
|
||||
SizedWidgetLayoutItemType::Grow => {}
|
||||
SizedAtomicKind::Custom(_) => {}
|
||||
SizedAtomicKind::Grow => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,62 +154,217 @@ impl<'a> WidgetLayout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WLButton<'a> {
|
||||
wl: WidgetLayout<'a>,
|
||||
// pub struct WLButton<'a> {
|
||||
// wl: WidgetLayout<'a>,
|
||||
// }
|
||||
//
|
||||
// impl<'a> WLButton<'a> {
|
||||
// pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
// Self {
|
||||
// wl: WidgetLayout::new()
|
||||
// .sense(Sense::click())
|
||||
// .add(Item::default(), WidgetLayoutItemType::Text(text.into())),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub fn image(image: impl Into<Image<'a>>) -> Self {
|
||||
// Self {
|
||||
// wl: WidgetLayout::new().sense(Sense::click()).add(
|
||||
// Item::default(),
|
||||
// WidgetLayoutItemType::Image(image.into().max_size(Vec2::splat(16.0))),
|
||||
// ),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub fn image_and_text(image: impl Into<Image<'a>>, text: impl Into<WidgetText>) -> Self {
|
||||
// Self {
|
||||
// wl: WidgetLayout::new()
|
||||
// .sense(Sense::click())
|
||||
// .add(Item::default(), WidgetLayoutItemType::Image(image.into()))
|
||||
// .add(Item::default(), WidgetLayoutItemType::Text(text.into())),
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// pub fn right_text(mut self, text: impl Into<WidgetText>) -> Self {
|
||||
// self.wl = self
|
||||
// .wl
|
||||
// .add(Item::default(), WidgetLayoutItemType::Grow)
|
||||
// .add(Item::default(), WidgetLayoutItemType::Text(text.into()));
|
||||
// self
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<'a> Widget for WLButton<'a> {
|
||||
// fn ui(mut self, ui: &mut Ui) -> Response {
|
||||
// let response = ui.ctx().read_response(ui.next_auto_id());
|
||||
//
|
||||
// let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
||||
// ui.style().interact(&response)
|
||||
// });
|
||||
//
|
||||
// self.wl.frame = self
|
||||
// .wl
|
||||
// .frame
|
||||
// .inner_margin(ui.style().spacing.button_padding)
|
||||
// .fill(visuals.bg_fill)
|
||||
// .stroke(visuals.bg_stroke)
|
||||
// .corner_radius(visuals.corner_radius);
|
||||
//
|
||||
// self.wl.show(ui)
|
||||
// }
|
||||
// }
|
||||
|
||||
pub enum AtomicKind<'a> {
|
||||
Text(WidgetText),
|
||||
Image(Image<'a>),
|
||||
Custom(Vec2),
|
||||
Grow,
|
||||
}
|
||||
|
||||
impl<'a> WLButton<'a> {
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
wl: WidgetLayout::new()
|
||||
.sense(Sense::click())
|
||||
.add(Item::default(), WidgetLayoutItemType::Text(text.into())),
|
||||
}
|
||||
}
|
||||
pub struct Atomic<'a> {
|
||||
size: Option<Vec2>,
|
||||
grow: bool,
|
||||
pub kind: AtomicKind<'a>,
|
||||
}
|
||||
|
||||
pub fn image(image: impl Into<Image<'a>>) -> Self {
|
||||
Self {
|
||||
wl: WidgetLayout::new().sense(Sense::click()).add(
|
||||
Item::default(),
|
||||
WidgetLayoutItemType::Image(image.into().max_size(Vec2::splat(16.0))),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_and_text(image: impl Into<Image<'a>>, text: impl Into<WidgetText>) -> Self {
|
||||
Self {
|
||||
wl: WidgetLayout::new()
|
||||
.sense(Sense::click())
|
||||
.add(Item::default(), WidgetLayoutItemType::Image(image.into()))
|
||||
.add(Item::default(), WidgetLayoutItemType::Text(text.into())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn right_text(mut self, text: impl Into<WidgetText>) -> Self {
|
||||
self.wl = self
|
||||
.wl
|
||||
.add(Item::default(), WidgetLayoutItemType::Grow)
|
||||
.add(Item::default(), WidgetLayoutItemType::Text(text.into()));
|
||||
self
|
||||
pub fn a<'a>(i: impl Into<AtomicKind<'a>>) -> Atomic<'a> {
|
||||
Atomic {
|
||||
size: None,
|
||||
grow: false,
|
||||
kind: i.into(),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for WLButton<'a> {
|
||||
fn ui(mut self, ui: &mut Ui) -> Response {
|
||||
let response = ui.ctx().read_response(ui.next_auto_id());
|
||||
impl Atomic<'_> {
|
||||
// pub fn size(mut self, size: Vec2) -> Self {
|
||||
// self.size = Some(size);
|
||||
// self
|
||||
// }
|
||||
//
|
||||
// pub fn grow(mut self, grow: bool) -> Self {
|
||||
// self.grow = grow;
|
||||
// self
|
||||
// }
|
||||
}
|
||||
|
||||
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
||||
ui.style().interact(&response)
|
||||
});
|
||||
trait AtomicExt<'a> {
|
||||
fn a_size(self, size: Vec2) -> Atomic<'a>;
|
||||
fn a_grow(self, grow: bool) -> Atomic<'a>;
|
||||
}
|
||||
|
||||
self.wl.frame = self
|
||||
.wl
|
||||
.frame
|
||||
.inner_margin(ui.style().spacing.button_padding)
|
||||
.fill(visuals.bg_fill)
|
||||
.stroke(visuals.bg_stroke)
|
||||
.corner_radius(visuals.corner_radius);
|
||||
impl<'a, T> AtomicExt<'a> for T
|
||||
where
|
||||
T: Into<Atomic<'a>> + Sized,
|
||||
{
|
||||
fn a_size(self, size: Vec2) -> Atomic<'a> {
|
||||
let mut atomic = self.into();
|
||||
atomic.size = Some(size);
|
||||
atomic
|
||||
}
|
||||
|
||||
self.wl.show(ui)
|
||||
fn a_grow(self, grow: bool) -> Atomic<'a> {
|
||||
let mut atomic = self.into();
|
||||
atomic.grow = grow;
|
||||
atomic
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<T> for Atomic<'a>
|
||||
where
|
||||
T: Into<AtomicKind<'a>>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
Atomic {
|
||||
size: None,
|
||||
grow: false,
|
||||
kind: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Image<'a>> for AtomicKind<'a> {
|
||||
fn from(value: Image<'a>) -> Self {
|
||||
AtomicKind::Image(value)
|
||||
}
|
||||
}
|
||||
|
||||
// impl<'a> From<&str> for AtomicKind<'a> {
|
||||
// fn from(value: &str) -> Self {
|
||||
// AtomicKind::Text(value.into())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<'a, T> From<T> for AtomicKind<'a>
|
||||
where
|
||||
T: Into<WidgetText>,
|
||||
{
|
||||
fn from(value: T) -> Self {
|
||||
AtomicKind::Text(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Atomics<'a>(Vec<Atomic<'a>>);
|
||||
|
||||
impl<'a> Atomics<'a> {
|
||||
pub fn add(&mut self, atomic: impl Into<Atomic<'a>>) {
|
||||
self.0.push(atomic.into());
|
||||
}
|
||||
|
||||
pub fn add_front(&mut self, atomic: impl Into<Atomic<'a>>) {
|
||||
self.0.insert(0, atomic.into());
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Atomic<'a>> {
|
||||
self.0.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> IntoAtomics<'a> for T
|
||||
where
|
||||
T: Into<Atomic<'a>>,
|
||||
{
|
||||
fn collect(self, atomics: &mut Atomics<'a>) {
|
||||
atomics.add(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoAtomics<'a> {
|
||||
fn collect(self, atomics: &mut Atomics<'a>);
|
||||
|
||||
fn into_atomics(self) -> Atomics<'a>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let mut atomics = Atomics(Vec::new());
|
||||
self.collect(&mut atomics);
|
||||
atomics
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAtomics<'a> for Atomics<'a> {
|
||||
fn collect(self, atomics: &mut Atomics<'a>) {
|
||||
atomics.0.extend(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! all_the_atomics {
|
||||
($($T:ident),*) => {
|
||||
impl<'a, $($T),*> IntoAtomics<'a> for ($($T),*)
|
||||
where
|
||||
$($T: IntoAtomics<'a>),*
|
||||
{
|
||||
fn collect(self, atomics: &mut Atomics<'a>) {
|
||||
#[allow(non_snake_case)]
|
||||
let ($($T),*) = self;
|
||||
$($T.collect(atomics);)*
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
all_the_atomics!();
|
||||
all_the_atomics!(T0, T1);
|
||||
all_the_atomics!(T0, T1, T2);
|
||||
all_the_atomics!(T0, T1, T2, T3);
|
||||
all_the_atomics!(T0, T1, T2, T3, T4);
|
||||
all_the_atomics!(T0, T1, T2, T3, T4, T5);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{
|
||||
widgets, Align, Color32, CornerRadius, FontSelection, Image, NumExt, Rect, Response, Sense,
|
||||
Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType,
|
||||
widgets, Align, Atomic, AtomicKind, Color32, CornerRadius, Frame, Image, IntoAtomics, NumExt,
|
||||
Rect, Response, Sense, Stroke, TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo,
|
||||
WidgetLayout, WidgetText, WidgetType,
|
||||
};
|
||||
|
||||
/// Clickable button with text.
|
||||
@@ -23,26 +24,35 @@ use crate::{
|
||||
/// ```
|
||||
#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
|
||||
pub struct Button<'a> {
|
||||
image: Option<Image<'a>>,
|
||||
text: Option<WidgetText>,
|
||||
right_text: WidgetText,
|
||||
wrap_mode: Option<TextWrapMode>,
|
||||
|
||||
/// None means default for interact
|
||||
fill: Option<Color32>,
|
||||
stroke: Option<Stroke>,
|
||||
sense: Sense,
|
||||
small: bool,
|
||||
frame: Option<bool>,
|
||||
min_size: Vec2,
|
||||
corner_radius: Option<CornerRadius>,
|
||||
selected: bool,
|
||||
image_tint_follows_text_color: bool,
|
||||
|
||||
wl: WidgetLayout<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Button<'a> {
|
||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||
Self::opt_image_and_text(None, Some(text.into()))
|
||||
pub fn new(text: impl IntoAtomics<'a>) -> Self {
|
||||
Self {
|
||||
wrap_mode: None,
|
||||
fill: None,
|
||||
stroke: None,
|
||||
small: false,
|
||||
frame: None,
|
||||
min_size: Vec2::ZERO,
|
||||
corner_radius: None,
|
||||
selected: false,
|
||||
image_tint_follows_text_color: false,
|
||||
wl: WidgetLayout::new(text.into_atomics()).sense(Sense::click()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a button with an image. The size of the image as displayed is defined by the provided size.
|
||||
@@ -58,21 +68,14 @@ impl<'a> Button<'a> {
|
||||
}
|
||||
|
||||
pub fn opt_image_and_text(image: Option<Image<'a>>, text: Option<WidgetText>) -> Self {
|
||||
Self {
|
||||
text,
|
||||
image,
|
||||
right_text: Default::default(),
|
||||
wrap_mode: None,
|
||||
fill: None,
|
||||
stroke: None,
|
||||
sense: Sense::click(),
|
||||
small: false,
|
||||
frame: None,
|
||||
min_size: Vec2::ZERO,
|
||||
corner_radius: None,
|
||||
selected: false,
|
||||
image_tint_follows_text_color: false,
|
||||
let mut button = Self::new(());
|
||||
if let Some(image) = image {
|
||||
button.wl.atomics.add(image);
|
||||
}
|
||||
if let Some(text) = text {
|
||||
button.wl.atomics.add(text);
|
||||
}
|
||||
button
|
||||
}
|
||||
|
||||
/// Set the wrap mode for the text.
|
||||
@@ -106,7 +109,6 @@ impl<'a> Button<'a> {
|
||||
#[inline]
|
||||
pub fn fill(mut self, fill: impl Into<Color32>) -> Self {
|
||||
self.fill = Some(fill.into());
|
||||
self.frame = Some(true);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -122,9 +124,6 @@ impl<'a> Button<'a> {
|
||||
/// Make this a small button, suitable for embedding into text.
|
||||
#[inline]
|
||||
pub fn small(mut self) -> Self {
|
||||
if let Some(text) = self.text {
|
||||
self.text = Some(text.text_style(TextStyle::Body));
|
||||
}
|
||||
self.small = true;
|
||||
self
|
||||
}
|
||||
@@ -140,7 +139,7 @@ impl<'a> Button<'a> {
|
||||
/// Change this to a drag-button with `Sense::drag()`.
|
||||
#[inline]
|
||||
pub fn sense(mut self, sense: Sense) -> Self {
|
||||
self.sense = sense;
|
||||
self.wl.sense = sense;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -184,15 +183,15 @@ impl<'a> Button<'a> {
|
||||
///
|
||||
/// See also [`Self::right_text`].
|
||||
#[inline]
|
||||
pub fn shortcut_text(mut self, shortcut_text: impl Into<WidgetText>) -> Self {
|
||||
self.right_text = shortcut_text.into().weak();
|
||||
pub fn shortcut_text(mut self, shortcut_text: impl Into<Atomic<'a>>) -> Self {
|
||||
self.wl = self.wl.add(shortcut_text);
|
||||
self
|
||||
}
|
||||
|
||||
/// Show some text on the right side of the button.
|
||||
#[inline]
|
||||
pub fn right_text(mut self, right_text: impl Into<WidgetText>) -> Self {
|
||||
self.right_text = right_text.into();
|
||||
pub fn right_text(mut self, right_text: impl Into<Atomic<'a>>) -> Self {
|
||||
self.wl = self.wl.add(right_text.into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -205,225 +204,237 @@ impl<'a> Button<'a> {
|
||||
}
|
||||
|
||||
impl Widget for Button<'_> {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
fn ui(mut self, ui: &mut Ui) -> Response {
|
||||
let Button {
|
||||
text,
|
||||
image,
|
||||
right_text,
|
||||
wrap_mode,
|
||||
fill,
|
||||
stroke,
|
||||
sense,
|
||||
small,
|
||||
frame,
|
||||
min_size,
|
||||
corner_radius,
|
||||
selected,
|
||||
image_tint_follows_text_color,
|
||||
mut wl,
|
||||
} = self;
|
||||
|
||||
let frame = frame.unwrap_or_else(|| ui.visuals().button_frame);
|
||||
let has_frame = frame.unwrap_or_else(|| ui.visuals().button_frame);
|
||||
|
||||
let default_font_height = || {
|
||||
let font_selection = FontSelection::default();
|
||||
let font_id = font_selection.resolve(ui.style());
|
||||
ui.fonts(|f| f.row_height(&font_id))
|
||||
};
|
||||
|
||||
let text_font_height = ui
|
||||
.fonts(|fonts| text.as_ref().map(|wt| wt.font_height(fonts, ui.style())))
|
||||
.unwrap_or_else(default_font_height);
|
||||
|
||||
let mut button_padding = if frame {
|
||||
let mut button_padding = if has_frame {
|
||||
ui.spacing().button_padding
|
||||
} else {
|
||||
Vec2::ZERO
|
||||
};
|
||||
if small {
|
||||
button_padding.y = 0.0;
|
||||
wl.atomics.iter_mut().for_each(|a| match &mut a.kind {
|
||||
AtomicKind::Text(text) => {
|
||||
*text = std::mem::take(text).small();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
|
||||
let (space_available_for_image, right_text_font_height) = if let Some(text) = &text {
|
||||
let font_height = ui.fonts(|fonts| text.font_height(fonts, ui.style()));
|
||||
(
|
||||
Vec2::splat(font_height), // Reasonable?
|
||||
font_height,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
ui.available_size() - 2.0 * button_padding,
|
||||
default_font_height(),
|
||||
)
|
||||
};
|
||||
let response = ui.ctx().read_response(ui.next_auto_id());
|
||||
|
||||
let image_size = if let Some(image) = &image {
|
||||
image
|
||||
.load_and_calc_size(ui, space_available_for_image)
|
||||
.unwrap_or(space_available_for_image)
|
||||
} else {
|
||||
Vec2::ZERO
|
||||
};
|
||||
|
||||
let gap_before_right_text = ui.spacing().item_spacing.x;
|
||||
|
||||
let mut text_wrap_width = ui.available_width() - 2.0 * button_padding.x;
|
||||
if image.is_some() {
|
||||
text_wrap_width -= image_size.x + ui.spacing().icon_spacing;
|
||||
}
|
||||
|
||||
// Note: we don't wrap the right text
|
||||
let right_galley = (!right_text.is_empty()).then(|| {
|
||||
right_text.into_galley(
|
||||
ui,
|
||||
Some(TextWrapMode::Extend),
|
||||
f32::INFINITY,
|
||||
TextStyle::Button,
|
||||
)
|
||||
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
||||
ui.style().interact(&response)
|
||||
});
|
||||
|
||||
if let Some(right_galley) = &right_galley {
|
||||
// Leave space for the right text:
|
||||
text_wrap_width -= gap_before_right_text + right_galley.size().x;
|
||||
}
|
||||
wl.frame = if has_frame {
|
||||
wl.frame
|
||||
.inner_margin(button_padding)
|
||||
.fill(fill.unwrap_or(visuals.bg_fill))
|
||||
.stroke(stroke.unwrap_or(visuals.bg_stroke))
|
||||
.corner_radius(corner_radius.unwrap_or(visuals.corner_radius))
|
||||
} else {
|
||||
Frame::new()
|
||||
};
|
||||
|
||||
let galley =
|
||||
text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button));
|
||||
let response = wl.show(ui);
|
||||
|
||||
let mut desired_size = Vec2::ZERO;
|
||||
if image.is_some() {
|
||||
desired_size.x += image_size.x;
|
||||
desired_size.y = desired_size.y.max(image_size.y);
|
||||
}
|
||||
if image.is_some() && galley.is_some() {
|
||||
desired_size.x += ui.spacing().icon_spacing;
|
||||
}
|
||||
if let Some(galley) = &galley {
|
||||
desired_size.x += galley.size().x;
|
||||
desired_size.y = desired_size.y.max(galley.size().y).max(text_font_height);
|
||||
}
|
||||
if let Some(right_galley) = &right_galley {
|
||||
desired_size.x += gap_before_right_text + right_galley.size().x;
|
||||
desired_size.y = desired_size
|
||||
.y
|
||||
.max(right_galley.size().y)
|
||||
.max(right_text_font_height);
|
||||
}
|
||||
desired_size += 2.0 * button_padding;
|
||||
if !small {
|
||||
desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
}
|
||||
desired_size = desired_size.at_least(min_size);
|
||||
// TODO: How to get text?
|
||||
// response.widget_info(|| {
|
||||
// if let Some(galley) = &galley {
|
||||
// WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), galley.text())
|
||||
// } else {
|
||||
// WidgetInfo::new(WidgetType::Button)
|
||||
// }
|
||||
// });
|
||||
|
||||
let (rect, mut response) = ui.allocate_at_least(desired_size, sense);
|
||||
response.widget_info(|| {
|
||||
if let Some(galley) = &galley {
|
||||
WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), galley.text())
|
||||
} else {
|
||||
WidgetInfo::new(WidgetType::Button)
|
||||
}
|
||||
});
|
||||
//
|
||||
// let space_available_for_image = if let Some(text) = &text {
|
||||
// let font_height = ui.fonts(|fonts| text.font_height(fonts, ui.style()));
|
||||
// Vec2::splat(font_height) // Reasonable?
|
||||
// } else {
|
||||
// ui.available_size() - 2.0 * button_padding
|
||||
// };
|
||||
//
|
||||
// let image_size = if let Some(image) = &image {
|
||||
// image
|
||||
// .load_and_calc_size(ui, space_available_for_image)
|
||||
// .unwrap_or(space_available_for_image)
|
||||
// } else {
|
||||
// Vec2::ZERO
|
||||
// };
|
||||
//
|
||||
// let gap_before_right_text = ui.spacing().item_spacing.x;
|
||||
//
|
||||
// let mut text_wrap_width = ui.available_width() - 2.0 * button_padding.x;
|
||||
// if image.is_some() {
|
||||
// text_wrap_width -= image_size.x + ui.spacing().icon_spacing;
|
||||
// }
|
||||
//
|
||||
// // Note: we don't wrap the right text
|
||||
// let right_galley = (!right_text.is_empty()).then(|| {
|
||||
// right_text.into_galley(
|
||||
// ui,
|
||||
// Some(TextWrapMode::Extend),
|
||||
// f32::INFINITY,
|
||||
// TextStyle::Button,
|
||||
// )
|
||||
// });
|
||||
//
|
||||
// if let Some(right_galley) = &right_galley {
|
||||
// // Leave space for the right text:
|
||||
// text_wrap_width -= gap_before_right_text + right_galley.size().x;
|
||||
// }
|
||||
//
|
||||
// let galley =
|
||||
// text.map(|text| text.into_galley(ui, wrap_mode, text_wrap_width, TextStyle::Button));
|
||||
//
|
||||
// let mut desired_size = Vec2::ZERO;
|
||||
// if image.is_some() {
|
||||
// desired_size.x += image_size.x;
|
||||
// desired_size.y = desired_size.y.max(image_size.y);
|
||||
// }
|
||||
// if image.is_some() && galley.is_some() {
|
||||
// desired_size.x += ui.spacing().icon_spacing;
|
||||
// }
|
||||
// if let Some(galley) = &galley {
|
||||
// desired_size.x += galley.size().x;
|
||||
// desired_size.y = desired_size.y.max(galley.size().y);
|
||||
// }
|
||||
// if let Some(right_galley) = &right_galley {
|
||||
// desired_size.x += gap_before_right_text + right_galley.size().x;
|
||||
// desired_size.y = desired_size.y.max(right_galley.size().y);
|
||||
// }
|
||||
// desired_size += 2.0 * button_padding;
|
||||
// if !small {
|
||||
// desired_size.y = desired_size.y.at_least(ui.spacing().interact_size.y);
|
||||
// }
|
||||
// desired_size = desired_size.at_least(min_size);
|
||||
//
|
||||
// let (rect, mut response) = ui.allocate_at_least(desired_size, sense);
|
||||
// response.widget_info(|| {
|
||||
// if let Some(galley) = &galley {
|
||||
// WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), galley.text())
|
||||
// } else {
|
||||
// WidgetInfo::new(WidgetType::Button)
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// if ui.is_rect_visible(rect) {
|
||||
// let visuals = ui.style().interact(&response);
|
||||
//
|
||||
// let (frame_expansion, frame_cr, frame_fill, frame_stroke) = if selected {
|
||||
// let selection = ui.visuals().selection;
|
||||
// (
|
||||
// Vec2::ZERO,
|
||||
// CornerRadius::ZERO,
|
||||
// selection.bg_fill,
|
||||
// selection.stroke,
|
||||
// )
|
||||
// } else if frame {
|
||||
// let expansion = Vec2::splat(visuals.expansion);
|
||||
// (
|
||||
// expansion,
|
||||
// visuals.corner_radius,
|
||||
// visuals.weak_bg_fill,
|
||||
// visuals.bg_stroke,
|
||||
// )
|
||||
// } else {
|
||||
// Default::default()
|
||||
// };
|
||||
// let frame_cr = corner_radius.unwrap_or(frame_cr);
|
||||
// let frame_fill = fill.unwrap_or(frame_fill);
|
||||
// let frame_stroke = stroke.unwrap_or(frame_stroke);
|
||||
// ui.painter().rect(
|
||||
// rect.expand2(frame_expansion),
|
||||
// frame_cr,
|
||||
// frame_fill,
|
||||
// frame_stroke,
|
||||
// epaint::StrokeKind::Inside,
|
||||
// );
|
||||
//
|
||||
// let mut cursor_x = rect.min.x + button_padding.x;
|
||||
//
|
||||
// if let Some(image) = &image {
|
||||
// let mut image_pos = ui
|
||||
// .layout()
|
||||
// .align_size_within_rect(image_size, rect.shrink2(button_padding))
|
||||
// .min;
|
||||
// if galley.is_some() || right_galley.is_some() {
|
||||
// image_pos.x = cursor_x;
|
||||
// }
|
||||
// let image_rect = Rect::from_min_size(image_pos, image_size);
|
||||
// cursor_x += image_size.x;
|
||||
// let tlr = image.load_for_size(ui.ctx(), image_size);
|
||||
// let mut image_options = image.image_options().clone();
|
||||
// if image_tint_follows_text_color {
|
||||
// image_options.tint = image_options.tint * visuals.text_color();
|
||||
// }
|
||||
// widgets::image::paint_texture_load_result(
|
||||
// ui,
|
||||
// &tlr,
|
||||
// image_rect,
|
||||
// image.show_loading_spinner,
|
||||
// &image_options,
|
||||
// None,
|
||||
// );
|
||||
// response = widgets::image::texture_load_result_response(
|
||||
// &image.source(ui.ctx()),
|
||||
// &tlr,
|
||||
// response,
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// if image.is_some() && galley.is_some() {
|
||||
// cursor_x += ui.spacing().icon_spacing;
|
||||
// }
|
||||
//
|
||||
// if let Some(galley) = galley {
|
||||
// let mut text_pos = ui
|
||||
// .layout()
|
||||
// .align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
||||
// .min;
|
||||
// if image.is_some() || right_galley.is_some() {
|
||||
// text_pos.x = cursor_x;
|
||||
// }
|
||||
// ui.painter().galley(text_pos, galley, visuals.text_color());
|
||||
// }
|
||||
//
|
||||
// if let Some(right_galley) = right_galley {
|
||||
// // Always align to the right
|
||||
// let layout = if ui.layout().is_horizontal() {
|
||||
// ui.layout().with_main_align(Align::Max)
|
||||
// } else {
|
||||
// ui.layout().with_cross_align(Align::Max)
|
||||
// };
|
||||
// let right_text_pos = layout
|
||||
// .align_size_within_rect(right_galley.size(), rect.shrink2(button_padding))
|
||||
// .min;
|
||||
//
|
||||
// ui.painter()
|
||||
// .galley(right_text_pos, right_galley, visuals.text_color());
|
||||
// }
|
||||
// }
|
||||
|
||||
if ui.is_rect_visible(rect) {
|
||||
let visuals = ui.style().interact(&response);
|
||||
|
||||
let (frame_expansion, frame_cr, frame_fill, frame_stroke) = if selected {
|
||||
let selection = ui.visuals().selection;
|
||||
(
|
||||
Vec2::ZERO,
|
||||
CornerRadius::ZERO,
|
||||
selection.bg_fill,
|
||||
selection.stroke,
|
||||
)
|
||||
} else if frame {
|
||||
let expansion = Vec2::splat(visuals.expansion);
|
||||
(
|
||||
expansion,
|
||||
visuals.corner_radius,
|
||||
visuals.weak_bg_fill,
|
||||
visuals.bg_stroke,
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
let frame_cr = corner_radius.unwrap_or(frame_cr);
|
||||
let frame_fill = fill.unwrap_or(frame_fill);
|
||||
let frame_stroke = stroke.unwrap_or(frame_stroke);
|
||||
ui.painter().rect(
|
||||
rect.expand2(frame_expansion),
|
||||
frame_cr,
|
||||
frame_fill,
|
||||
frame_stroke,
|
||||
epaint::StrokeKind::Inside,
|
||||
);
|
||||
|
||||
let mut cursor_x = rect.min.x + button_padding.x;
|
||||
|
||||
if let Some(image) = &image {
|
||||
let mut image_pos = ui
|
||||
.layout()
|
||||
.align_size_within_rect(image_size, rect.shrink2(button_padding))
|
||||
.min;
|
||||
if galley.is_some() || right_galley.is_some() {
|
||||
image_pos.x = cursor_x;
|
||||
}
|
||||
let image_rect = Rect::from_min_size(image_pos, image_size);
|
||||
cursor_x += image_size.x;
|
||||
let tlr = image.load_for_size(ui.ctx(), image_size);
|
||||
let mut image_options = image.image_options().clone();
|
||||
if image_tint_follows_text_color {
|
||||
image_options.tint = image_options.tint * visuals.text_color();
|
||||
}
|
||||
widgets::image::paint_texture_load_result(
|
||||
ui,
|
||||
&tlr,
|
||||
image_rect,
|
||||
image.show_loading_spinner,
|
||||
&image_options,
|
||||
None,
|
||||
);
|
||||
response = widgets::image::texture_load_result_response(
|
||||
&image.source(ui.ctx()),
|
||||
&tlr,
|
||||
response,
|
||||
);
|
||||
}
|
||||
|
||||
if image.is_some() && galley.is_some() {
|
||||
cursor_x += ui.spacing().icon_spacing;
|
||||
}
|
||||
|
||||
if let Some(galley) = galley {
|
||||
let mut text_pos = ui
|
||||
.layout()
|
||||
.align_size_within_rect(galley.size(), rect.shrink2(button_padding))
|
||||
.min;
|
||||
if image.is_some() || right_galley.is_some() {
|
||||
text_pos.x = cursor_x;
|
||||
}
|
||||
ui.painter().galley(text_pos, galley, visuals.text_color());
|
||||
}
|
||||
|
||||
if let Some(right_galley) = right_galley {
|
||||
// Always align to the right
|
||||
let layout = if ui.layout().is_horizontal() {
|
||||
ui.layout().with_main_align(Align::Max)
|
||||
} else {
|
||||
ui.layout().with_cross_align(Align::Max)
|
||||
};
|
||||
let right_text_pos = layout
|
||||
.align_size_within_rect(right_galley.size(), rect.shrink2(button_padding))
|
||||
.min;
|
||||
|
||||
ui.painter()
|
||||
.galley(right_text_pos, right_galley, visuals.text_color());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cursor) = ui.visuals().interact_cursor {
|
||||
if response.hovered() {
|
||||
ui.ctx().set_cursor_icon(cursor);
|
||||
}
|
||||
}
|
||||
// if let Some(cursor) = ui.visuals().interact_cursor {
|
||||
// if response.hovered() {
|
||||
// ui.ctx().set_cursor_icon(cursor);
|
||||
// }
|
||||
// }
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
use eframe::egui;
|
||||
use eframe::egui::{
|
||||
include_image, Image, Key, KeyboardShortcut, ModifierNames, Modifiers, Popup, RichText,
|
||||
WLButton, Widget,
|
||||
include_image, Button, Image, Key, KeyboardShortcut, ModifierNames, Modifiers, Popup, RichText,
|
||||
Widget,
|
||||
};
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
@@ -34,15 +34,17 @@ fn main() -> eframe::Result {
|
||||
}
|
||||
ui.label(format!("Hello '{name}', age {age}"));
|
||||
|
||||
if WLButton::new("WL Button").ui(ui).clicked() {
|
||||
if Button::new("WL Button").ui(ui).clicked() {
|
||||
age += 1;
|
||||
};
|
||||
|
||||
let source = include_image!("../../../crates/eframe/data/icon.png");
|
||||
let response = WLButton::image_and_text(source, "Hello World").ui(ui);
|
||||
let response = Button::image_and_text(source.clone(), "Hello World").ui(ui);
|
||||
|
||||
Button::new((Image::new(source).tint(egui::Color32::RED), "Tuple Button")).ui(ui);
|
||||
|
||||
Popup::menu(&response).show(|ui| {
|
||||
WLButton::new("Print")
|
||||
Button::new("Print")
|
||||
.right_text(
|
||||
RichText::new(
|
||||
KeyboardShortcut::new(Modifiers::COMMAND, Key::P)
|
||||
@@ -51,7 +53,7 @@ fn main() -> eframe::Result {
|
||||
.weak(),
|
||||
)
|
||||
.ui(ui);
|
||||
WLButton::new("A very long button")
|
||||
Button::new("A very long button")
|
||||
.right_text(
|
||||
RichText::new(
|
||||
KeyboardShortcut::new(Modifiers::COMMAND, Key::O)
|
||||
|
||||
Reference in New Issue
Block a user