mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -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"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui_extras",
|
||||
"env_logger",
|
||||
"image",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -508,6 +508,7 @@ pub use self::{
|
||||
ui_builder::UiBuilder,
|
||||
ui_stack::*,
|
||||
viewport::*,
|
||||
widget_layout::*,
|
||||
widget_rect::{WidgetRect, WidgetRects},
|
||||
widget_text::{RichText, WidgetText},
|
||||
widgets::*,
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
use crate::{Frame, ImageSource, Response, Sense, Ui, WidgetText};
|
||||
use emath::Vec2;
|
||||
use crate::{Frame, Image, ImageSource, Response, Sense, TextStyle, Ui, Widget, WidgetText};
|
||||
use emath::{Align2, Vec2};
|
||||
use epaint::Galley;
|
||||
use std::sync::Arc;
|
||||
|
||||
enum WidgetLayoutItem<'a> {
|
||||
enum WidgetLayoutItemType<'a> {
|
||||
Text(WidgetText),
|
||||
Image(ImageSource<'a>),
|
||||
Image(Image<'a>),
|
||||
Custom(Vec2),
|
||||
Grow,
|
||||
}
|
||||
|
||||
enum SizedWidgetLayoutItem<'a> {
|
||||
Text(Galley),
|
||||
Image(ImageSource<'a>, Vec2),
|
||||
enum SizedWidgetLayoutItemType<'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<'_> {
|
||||
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> {
|
||||
items: Vec<WidgetLayoutItem<'a>>,
|
||||
items: Vec<(Item, WidgetLayoutItemType<'a>)>,
|
||||
gap: f32,
|
||||
frame: Frame,
|
||||
sense: Sense,
|
||||
@@ -27,14 +51,14 @@ impl<'a> WidgetLayout<'a> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: Vec::new(),
|
||||
gap: 0.0,
|
||||
gap: 4.0,
|
||||
frame: Frame::default(),
|
||||
sense: Sense::hover(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(mut self, item: impl Into<WidgetLayoutItem<'a>>) -> Self {
|
||||
self.items.push(item.into());
|
||||
pub fn add(mut self, item: Item, kind: impl Into<WidgetLayoutItemType<'a>>) -> Self {
|
||||
self.items.push((item, kind.into()));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -54,33 +78,143 @@ impl<'a> WidgetLayout<'a> {
|
||||
}
|
||||
|
||||
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 preferred_width = 0.0;
|
||||
|
||||
let mut height = 0.0;
|
||||
let mut height: f32 = 0.0;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
struct WLButton<'a> {
|
||||
pub struct WLButton<'a> {
|
||||
wl: WidgetLayout<'a>,
|
||||
}
|
||||
|
||||
impl<'a> WLButton<'a> {
|
||||
pub fn new(text: impl Into<WidgetText>) -> 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 visuals = response.map_or(&ui.style().visuals.widgets.inactive, |response| {
|
||||
@@ -90,6 +224,7 @@ impl<'a> WLButton<'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);
|
||||
|
||||
@@ -20,3 +20,5 @@ env_logger = { version = "0.10", default-features = false, features = [
|
||||
"auto-color",
|
||||
"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
|
||||
|
||||
use eframe::egui;
|
||||
use eframe::egui::{
|
||||
include_image, Image, Key, KeyboardShortcut, ModifierNames, Modifiers, Popup, RichText,
|
||||
WLButton, Widget,
|
||||
};
|
||||
|
||||
fn main() -> eframe::Result {
|
||||
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| {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
egui_extras::install_image_loaders(ctx);
|
||||
ui.heading("My egui Application");
|
||||
ui.horizontal(|ui| {
|
||||
let name_label = ui.label("Your name: ");
|
||||
@@ -28,6 +33,34 @@ fn main() -> eframe::Result {
|
||||
age += 1;
|
||||
}
|
||||
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