mirror of
https://github.com/emilk/egui.git
synced 2026-08-31 13:50:04 -04:00
Finish WidgetLayout prototype
This commit is contained in:
committed by
lucasmerlin
parent
fa81ae049e
commit
4dbf20d107
@@ -2064,7 +2064,9 @@ name = "hello_world_simple"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eframe",
|
"eframe",
|
||||||
|
"egui_extras",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"image",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -508,6 +508,7 @@ pub use self::{
|
|||||||
ui_builder::UiBuilder,
|
ui_builder::UiBuilder,
|
||||||
ui_stack::*,
|
ui_stack::*,
|
||||||
viewport::*,
|
viewport::*,
|
||||||
|
widget_layout::*,
|
||||||
widget_rect::{WidgetRect, WidgetRects},
|
widget_rect::{WidgetRect, WidgetRects},
|
||||||
widget_text::{RichText, WidgetText},
|
widget_text::{RichText, WidgetText},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
|
|||||||
@@ -1,23 +1,47 @@
|
|||||||
use crate::{Frame, ImageSource, Response, Sense, Ui, WidgetText};
|
use crate::{Frame, Image, ImageSource, Response, Sense, TextStyle, Ui, Widget, WidgetText};
|
||||||
use emath::Vec2;
|
use emath::{Align2, Vec2};
|
||||||
use epaint::Galley;
|
use epaint::Galley;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
enum WidgetLayoutItem<'a> {
|
enum WidgetLayoutItemType<'a> {
|
||||||
Text(WidgetText),
|
Text(WidgetText),
|
||||||
Image(ImageSource<'a>),
|
Image(Image<'a>),
|
||||||
Custom(Vec2),
|
Custom(Vec2),
|
||||||
Grow,
|
Grow,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SizedWidgetLayoutItem<'a> {
|
enum SizedWidgetLayoutItemType<'a> {
|
||||||
Text(Galley),
|
Text(Arc<Galley>),
|
||||||
Image(ImageSource<'a>, Vec2),
|
Image(Image<'a>, Vec2),
|
||||||
Custom(Vec2),
|
Custom(Vec2),
|
||||||
Grow,
|
Grow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Item {
|
||||||
|
align2: Align2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Item {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
align2: Align2::LEFT_CENTER,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SizedWidgetLayoutItemType<'_> {
|
||||||
|
pub fn size(&self) -> Vec2 {
|
||||||
|
match self {
|
||||||
|
SizedWidgetLayoutItemType::Text(galley) => galley.size(),
|
||||||
|
SizedWidgetLayoutItemType::Image(_, size) => *size,
|
||||||
|
SizedWidgetLayoutItemType::Custom(size) => *size,
|
||||||
|
SizedWidgetLayoutItemType::Grow => Vec2::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct WidgetLayout<'a> {
|
struct WidgetLayout<'a> {
|
||||||
items: Vec<WidgetLayoutItem<'a>>,
|
items: Vec<(Item, WidgetLayoutItemType<'a>)>,
|
||||||
gap: f32,
|
gap: f32,
|
||||||
frame: Frame,
|
frame: Frame,
|
||||||
sense: Sense,
|
sense: Sense,
|
||||||
@@ -27,14 +51,14 @@ impl<'a> WidgetLayout<'a> {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
gap: 0.0,
|
gap: 4.0,
|
||||||
frame: Frame::default(),
|
frame: Frame::default(),
|
||||||
sense: Sense::hover(),
|
sense: Sense::hover(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(mut self, item: impl Into<WidgetLayoutItem<'a>>) -> Self {
|
pub fn add(mut self, item: Item, kind: impl Into<WidgetLayoutItemType<'a>>) -> Self {
|
||||||
self.items.push(item.into());
|
self.items.push((item, kind.into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,33 +78,143 @@ impl<'a> WidgetLayout<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn show(self, ui: &mut Ui) -> Response {
|
pub fn show(self, ui: &mut Ui) -> Response {
|
||||||
let available_width = ui.available_width();
|
let available_size = ui.available_size();
|
||||||
|
let available_width = available_size.x;
|
||||||
|
|
||||||
let mut desired_width = 0.0;
|
let mut desired_width = 0.0;
|
||||||
let mut preferred_width = 0.0;
|
let mut preferred_width = 0.0;
|
||||||
|
|
||||||
let mut height = 0.0;
|
let mut height: f32 = 0.0;
|
||||||
|
|
||||||
let mut sized_items = Vec::new();
|
let mut sized_items = Vec::new();
|
||||||
|
|
||||||
let (rect, response) = ui.allocate_at_least(Vec2::new(desired_width, height), self.sense);
|
let mut grow_count = 0;
|
||||||
|
|
||||||
|
for (item, kind) in self.items {
|
||||||
|
let (preferred_size, sized) = match kind {
|
||||||
|
WidgetLayoutItemType::Text(text) => {
|
||||||
|
let galley = text.into_galley(ui, None, available_width, TextStyle::Button);
|
||||||
|
(
|
||||||
|
galley.size(), // TODO
|
||||||
|
SizedWidgetLayoutItemType::Text(galley),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
WidgetLayoutItemType::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))
|
||||||
|
}
|
||||||
|
WidgetLayoutItemType::Custom(size) => {
|
||||||
|
(size, SizedWidgetLayoutItemType::Custom(size))
|
||||||
|
}
|
||||||
|
WidgetLayoutItemType::Grow => {
|
||||||
|
grow_count += 1;
|
||||||
|
(Vec2::ZERO, SizedWidgetLayoutItemType::Grow)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let size = sized.size();
|
||||||
|
|
||||||
|
desired_width += size.x;
|
||||||
|
preferred_width += preferred_size.x;
|
||||||
|
|
||||||
|
height = height.max(size.y);
|
||||||
|
|
||||||
|
sized_items.push((item, sized));
|
||||||
|
}
|
||||||
|
|
||||||
|
if sized_items.len() > 1 {
|
||||||
|
let gap_space = self.gap * (sized_items.len() as f32 - 1.0);
|
||||||
|
desired_width += gap_space;
|
||||||
|
preferred_width += gap_space;
|
||||||
|
}
|
||||||
|
|
||||||
|
let margin = self.frame.total_margin();
|
||||||
|
let content_size = Vec2::new(desired_width, height);
|
||||||
|
let frame_size = content_size + margin.sum();
|
||||||
|
|
||||||
|
let (rect, response) = ui.allocate_at_least(frame_size, self.sense);
|
||||||
|
|
||||||
|
let content_rect = rect - margin;
|
||||||
|
ui.painter().add(self.frame.paint(content_rect));
|
||||||
|
|
||||||
|
let width_to_fill = content_rect.width();
|
||||||
|
let extra_space = f32::max(width_to_fill - desired_width, 0.0);
|
||||||
|
let grow_width = f32::max(extra_space / grow_count as f32, 0.0);
|
||||||
|
|
||||||
|
let mut cursor = content_rect.left();
|
||||||
|
|
||||||
|
for (item, sized) in sized_items {
|
||||||
|
let size = sized.size();
|
||||||
|
let width = match sized {
|
||||||
|
SizedWidgetLayoutItemType::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);
|
||||||
|
|
||||||
|
match sized {
|
||||||
|
SizedWidgetLayoutItemType::Text(galley) => {
|
||||||
|
ui.painter()
|
||||||
|
.galley(rect.min, galley, ui.visuals().text_color());
|
||||||
|
}
|
||||||
|
SizedWidgetLayoutItemType::Image(image, _) => {
|
||||||
|
image.paint_at(ui, rect);
|
||||||
|
}
|
||||||
|
SizedWidgetLayoutItemType::Custom(_) => {}
|
||||||
|
SizedWidgetLayoutItemType::Grow => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct WLButton<'a> {
|
pub struct WLButton<'a> {
|
||||||
wl: WidgetLayout<'a>,
|
wl: WidgetLayout<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> WLButton<'a> {
|
impl<'a> WLButton<'a> {
|
||||||
pub fn new(text: impl Into<WidgetText>) -> Self {
|
pub fn new(text: impl Into<WidgetText>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
wl: WidgetLayout::new().add(text),
|
wl: WidgetLayout::new()
|
||||||
|
.sense(Sense::click())
|
||||||
|
.add(Item::default(), WidgetLayoutItemType::Text(text.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui(mut self, ui: &mut Ui) -> Response {
|
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 response = ui.ctx().read_response(ui.next_auto_id());
|
||||||
|
|
||||||
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
let visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
||||||
@@ -90,6 +224,7 @@ impl<'a> WLButton<'a> {
|
|||||||
self.wl.frame = self
|
self.wl.frame = self
|
||||||
.wl
|
.wl
|
||||||
.frame
|
.frame
|
||||||
|
.inner_margin(ui.style().spacing.button_padding)
|
||||||
.fill(visuals.bg_fill)
|
.fill(visuals.bg_fill)
|
||||||
.stroke(visuals.bg_stroke)
|
.stroke(visuals.bg_stroke)
|
||||||
.corner_radius(visuals.corner_radius);
|
.corner_radius(visuals.corner_radius);
|
||||||
|
|||||||
@@ -20,3 +20,5 @@ env_logger = { version = "0.10", default-features = false, features = [
|
|||||||
"auto-color",
|
"auto-color",
|
||||||
"humantime",
|
"humantime",
|
||||||
] }
|
] }
|
||||||
|
egui_extras = {workspace = true, features = ["image", "all_loaders"]}
|
||||||
|
image = {workspace = true, features = ["png"]}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
||||||
|
|
||||||
use eframe::egui;
|
use eframe::egui;
|
||||||
|
use eframe::egui::{
|
||||||
|
include_image, Image, Key, KeyboardShortcut, ModifierNames, Modifiers, Popup, RichText,
|
||||||
|
WLButton, Widget,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
fn main() -> eframe::Result {
|
||||||
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||||
@@ -17,6 +21,7 @@ fn main() -> eframe::Result {
|
|||||||
|
|
||||||
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
|
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
egui_extras::install_image_loaders(ctx);
|
||||||
ui.heading("My egui Application");
|
ui.heading("My egui Application");
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let name_label = ui.label("Your name: ");
|
let name_label = ui.label("Your name: ");
|
||||||
@@ -28,6 +33,34 @@ fn main() -> eframe::Result {
|
|||||||
age += 1;
|
age += 1;
|
||||||
}
|
}
|
||||||
ui.label(format!("Hello '{name}', age {age}"));
|
ui.label(format!("Hello '{name}', age {age}"));
|
||||||
|
|
||||||
|
if WLButton::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);
|
||||||
|
|
||||||
|
Popup::menu(&response).show(|ui| {
|
||||||
|
WLButton::new("Print")
|
||||||
|
.right_text(
|
||||||
|
RichText::new(
|
||||||
|
KeyboardShortcut::new(Modifiers::COMMAND, Key::P)
|
||||||
|
.format(&ModifierNames::SYMBOLS, true),
|
||||||
|
)
|
||||||
|
.weak(),
|
||||||
|
)
|
||||||
|
.ui(ui);
|
||||||
|
WLButton::new("A very long button")
|
||||||
|
.right_text(
|
||||||
|
RichText::new(
|
||||||
|
KeyboardShortcut::new(Modifiers::COMMAND, Key::O)
|
||||||
|
.format(&ModifierNames::SYMBOLS, true),
|
||||||
|
)
|
||||||
|
.weak(),
|
||||||
|
)
|
||||||
|
.ui(ui);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user