Add Ui functions for doing manual layout ("put this widget here")
This commit is contained in:
parent
bca722ddf8
commit
df4c0257c0
8 changed files with 231 additions and 65 deletions
|
@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
* Add support for secondary and middle mouse buttons.
|
||||
* Add `Label` methods for code, strong, strikethrough, underline and italics.
|
||||
* Add `ui.group(|ui| { … })` to visually group some widgets within a frame.
|
||||
* Add `Ui` helpers for doing manual layout (`ui.put`, `ui.allocate_ui_at_rect` and more).
|
||||
* Add `ui.set_enabled(false)` to disable all widgets in a `Ui` (grayed out and non-interactive).
|
||||
* Add `TextEdit::hint_text` for showing a weak hint text when empty.
|
||||
* `egui::popup::popup_below_widget`: show a popup area below another widget.
|
||||
|
|
|
@ -136,7 +136,7 @@ impl GridLayout {
|
|||
Align2::LEFT_CENTER.align_size_within_rect(size, frame)
|
||||
}
|
||||
|
||||
pub(crate) fn justify_or_align(&self, frame: Rect, size: Vec2) -> Rect {
|
||||
pub(crate) fn justify_and_align(&self, frame: Rect, size: Vec2) -> Rect {
|
||||
self.align_size_within_rect(size, frame)
|
||||
}
|
||||
|
||||
|
|
|
@ -115,6 +115,12 @@ pub struct Layout {
|
|||
/// wrap to a new row when we reach the right side of the `max_rect`.
|
||||
main_wrap: bool,
|
||||
|
||||
/// How to align things on the main axis.
|
||||
main_align: Align,
|
||||
|
||||
/// Justify the main axis?
|
||||
main_justify: bool,
|
||||
|
||||
/// How to align things on the cross axis.
|
||||
/// For vertical layouts: put things to left, center or right?
|
||||
/// For horizontal layouts: put things to top, center or bottom?
|
||||
|
@ -133,6 +139,8 @@ impl Default for Layout {
|
|||
Self {
|
||||
main_dir: Direction::TopDown,
|
||||
main_wrap: false,
|
||||
main_align: Align::TOP,
|
||||
main_justify: false,
|
||||
cross_align: Align::LEFT,
|
||||
cross_justify: false,
|
||||
}
|
||||
|
@ -145,6 +153,8 @@ impl Layout {
|
|||
Self {
|
||||
main_dir: Direction::LeftToRight,
|
||||
main_wrap: false,
|
||||
main_align: Align::Center, // looks best to e.g. center text within a button
|
||||
main_justify: false,
|
||||
cross_align: Align::Center,
|
||||
cross_justify: false,
|
||||
}
|
||||
|
@ -154,6 +164,8 @@ impl Layout {
|
|||
Self {
|
||||
main_dir: Direction::RightToLeft,
|
||||
main_wrap: false,
|
||||
main_align: Align::Center, // looks best to e.g. center text within a button
|
||||
main_justify: false,
|
||||
cross_align: Align::Center,
|
||||
cross_justify: false,
|
||||
}
|
||||
|
@ -163,6 +175,8 @@ impl Layout {
|
|||
Self {
|
||||
main_dir: Direction::TopDown,
|
||||
main_wrap: false,
|
||||
main_align: Align::Center, // looks best to e.g. center text within a button
|
||||
main_justify: false,
|
||||
cross_align,
|
||||
cross_justify: false,
|
||||
}
|
||||
|
@ -177,6 +191,8 @@ impl Layout {
|
|||
Self {
|
||||
main_dir: Direction::BottomUp,
|
||||
main_wrap: false,
|
||||
main_align: Align::Center, // looks best to e.g. center text within a button
|
||||
main_justify: false,
|
||||
cross_align,
|
||||
cross_justify: false,
|
||||
}
|
||||
|
@ -186,11 +202,24 @@ impl Layout {
|
|||
Self {
|
||||
main_dir,
|
||||
main_wrap: false,
|
||||
main_align: Align::Center, // looks best to e.g. center text within a button
|
||||
main_justify: false,
|
||||
cross_align,
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn centered_and_justified(main_dir: Direction) -> Self {
|
||||
Self {
|
||||
main_dir,
|
||||
main_wrap: false,
|
||||
main_align: Align::Center,
|
||||
main_justify: true,
|
||||
cross_align: Align::Center,
|
||||
cross_justify: true,
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated = "Use `top_down`"]
|
||||
pub fn vertical(cross_align: Align) -> Self {
|
||||
Self::top_down(cross_align)
|
||||
|
@ -252,22 +281,38 @@ impl Layout {
|
|||
}
|
||||
|
||||
fn horizontal_align(&self) -> Align {
|
||||
match self.main_dir {
|
||||
// Direction::LeftToRight => Align::LEFT,
|
||||
// Direction::RightToLeft => Align::right(),
|
||||
Direction::LeftToRight | Direction::RightToLeft => Align::Center, // looks better to e.g. center text within a button
|
||||
|
||||
Direction::TopDown | Direction::BottomUp => self.cross_align,
|
||||
if self.is_horizontal() {
|
||||
self.main_align
|
||||
} else {
|
||||
self.cross_align
|
||||
}
|
||||
}
|
||||
|
||||
fn vertical_align(&self) -> Align {
|
||||
match self.main_dir {
|
||||
// Direction::TopDown => Align::TOP,
|
||||
// Direction::BottomUp => Align::BOTTOM,
|
||||
Direction::TopDown | Direction::BottomUp => Align::Center, // looks better to e.g. center text within a button
|
||||
if self.is_vertical() {
|
||||
self.main_align
|
||||
} else {
|
||||
self.cross_align
|
||||
}
|
||||
}
|
||||
|
||||
Direction::LeftToRight | Direction::RightToLeft => self.cross_align,
|
||||
fn align2(&self) -> Align2 {
|
||||
Align2([self.horizontal_align(), self.vertical_align()])
|
||||
}
|
||||
|
||||
fn horizontal_justify(&self) -> bool {
|
||||
if self.is_horizontal() {
|
||||
self.main_justify
|
||||
} else {
|
||||
self.cross_justify
|
||||
}
|
||||
}
|
||||
|
||||
fn vertical_justify(&self) -> bool {
|
||||
if self.is_vertical() {
|
||||
self.main_justify
|
||||
} else {
|
||||
self.cross_justify
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,18 +320,7 @@ impl Layout {
|
|||
/// ## Doing layout
|
||||
impl Layout {
|
||||
pub fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
|
||||
let x = match self.horizontal_align() {
|
||||
Align::Min => outer.left(),
|
||||
Align::Center => outer.center().x - size.x / 2.0,
|
||||
Align::Max => outer.right() - size.x,
|
||||
};
|
||||
let y = match self.vertical_align() {
|
||||
Align::Min => outer.top(),
|
||||
Align::Center => outer.center().y - size.y / 2.0,
|
||||
Align::Max => outer.bottom() - size.y,
|
||||
};
|
||||
|
||||
Rect::from_min_size(Pos2::new(x, y), size)
|
||||
self.align2().align_size_within_rect(size, outer)
|
||||
}
|
||||
|
||||
fn initial_cursor(&self, max_rect: Rect) -> Pos2 {
|
||||
|
@ -369,7 +403,7 @@ impl Layout {
|
|||
/// Returns where to put the next widget that is of the given size.
|
||||
/// The returned `frame_rect` `Rect` will always be justified along the cross axis.
|
||||
/// This is what you then pass to `advance_after_rects`.
|
||||
/// Use `justify_or_align` to get the inner `widget_rect`.
|
||||
/// Use `justify_and_align` to get the inner `widget_rect`.
|
||||
#[allow(clippy::collapsible_if)]
|
||||
pub(crate) fn next_space(
|
||||
&self,
|
||||
|
@ -422,12 +456,12 @@ impl Layout {
|
|||
}
|
||||
|
||||
let available_size = self.available_size_before_wrap_finite(region);
|
||||
if self.main_dir.is_horizontal() {
|
||||
// Fill full height
|
||||
child_size.y = child_size.y.max(available_size.y);
|
||||
} else {
|
||||
// Fill full width
|
||||
child_size.x = child_size.x.max(available_size.x);
|
||||
|
||||
if self.is_vertical() || self.horizontal_justify() {
|
||||
child_size.x = child_size.x.at_least(available_size.x); // fill full width
|
||||
}
|
||||
if self.is_horizontal() || self.vertical_justify() {
|
||||
child_size.y = child_size.y.at_least(available_size.y); // fill full height
|
||||
}
|
||||
|
||||
let child_pos = match self.main_dir {
|
||||
|
@ -440,30 +474,15 @@ impl Layout {
|
|||
Rect::from_min_size(child_pos, child_size)
|
||||
}
|
||||
|
||||
/// Apply justify or alignment after calling `next_space`.
|
||||
pub(crate) fn justify_or_align(&self, rect: Rect, mut child_size: Vec2) -> Rect {
|
||||
if self.cross_justify {
|
||||
if self.main_dir.is_horizontal() {
|
||||
child_size.y = rect.height(); // fill full height
|
||||
} else {
|
||||
child_size.x = rect.width(); // fill full width
|
||||
}
|
||||
/// Apply justify (fill width/height) and/or alignment after calling `next_space`.
|
||||
pub(crate) fn justify_and_align(&self, rect: Rect, mut child_size: Vec2) -> Rect {
|
||||
if self.horizontal_justify() {
|
||||
child_size.x = child_size.x.at_least(rect.width()); // fill full width
|
||||
}
|
||||
|
||||
match self.main_dir {
|
||||
Direction::LeftToRight => {
|
||||
Align2([Align::Min, self.cross_align]).align_size_within_rect(child_size, rect)
|
||||
}
|
||||
Direction::RightToLeft => {
|
||||
Align2([Align::Max, self.cross_align]).align_size_within_rect(child_size, rect)
|
||||
}
|
||||
Direction::TopDown => {
|
||||
Align2([self.cross_align, Align::Min]).align_size_within_rect(child_size, rect)
|
||||
}
|
||||
Direction::BottomUp => {
|
||||
Align2([self.cross_align, Align::Max]).align_size_within_rect(child_size, rect)
|
||||
}
|
||||
if self.vertical_justify() {
|
||||
child_size.y = child_size.y.at_least(rect.height()); // fill full height
|
||||
}
|
||||
self.align_size_within_rect(child_size, rect)
|
||||
}
|
||||
|
||||
/// Advance the cursor by this many points.
|
||||
|
|
|
@ -102,7 +102,7 @@ impl Placer {
|
|||
/// Returns where to put the next widget that is of the given size.
|
||||
/// The returned `frame_rect` will always be justified along the cross axis.
|
||||
/// This is what you then pass to `advance_after_rects`.
|
||||
/// Use `justify_or_align` to get the inner `widget_rect`.
|
||||
/// Use `justify_and_align` to get the inner `widget_rect`.
|
||||
pub(crate) fn next_space(&self, child_size: Vec2, item_spacing: Vec2) -> Rect {
|
||||
if let Some(grid) = &self.grid {
|
||||
grid.next_cell(self.region.cursor, child_size)
|
||||
|
@ -113,11 +113,11 @@ impl Placer {
|
|||
}
|
||||
|
||||
/// Apply justify or alignment after calling `next_space`.
|
||||
pub(crate) fn justify_or_align(&self, rect: Rect, child_size: Vec2) -> Rect {
|
||||
pub(crate) fn justify_and_align(&self, rect: Rect, child_size: Vec2) -> Rect {
|
||||
if let Some(grid) = &self.grid {
|
||||
grid.justify_or_align(rect, child_size)
|
||||
grid.justify_and_align(rect, child_size)
|
||||
} else {
|
||||
self.layout.justify_or_align(rect, child_size)
|
||||
self.layout.justify_and_align(rect, child_size)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,9 @@ use crate::{
|
|||
/// ui.label("A shorter and more convenient way to add a label.");
|
||||
/// ui.horizontal(|ui| {
|
||||
/// ui.label("Add widgets");
|
||||
/// ui.button("on the same row!");
|
||||
/// if ui.button("on the same row!").clicked() {
|
||||
/// /* … */
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
pub struct Ui {
|
||||
|
@ -274,7 +276,7 @@ impl Ui {
|
|||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/// ## Sizes etc
|
||||
/// # Sizes etc
|
||||
impl Ui {
|
||||
/// Where and how large the `Ui` is already.
|
||||
/// All widgets that have been added ot this `Ui` fits within this rectangle.
|
||||
|
@ -517,7 +519,10 @@ impl Ui {
|
|||
pub fn advance_cursor(&mut self, amount: f32) {
|
||||
self.placer.advance_cursor(amount);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Allocating space: where do I put my widgets?
|
||||
impl Ui {
|
||||
/// Allocate space for a widget and check for interaction in the space.
|
||||
/// Returns a `Response` which contains a rectangle, id, and interaction info.
|
||||
///
|
||||
|
@ -629,7 +634,7 @@ impl Ui {
|
|||
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
|
||||
let item_spacing = self.spacing().item_spacing;
|
||||
let frame_rect = self.placer.next_space(desired_size, item_spacing);
|
||||
let widget_rect = self.placer.justify_or_align(frame_rect, desired_size);
|
||||
let widget_rect = self.placer.justify_and_align(frame_rect, desired_size);
|
||||
|
||||
self.placer
|
||||
.advance_after_rects(frame_rect, widget_rect, item_spacing);
|
||||
|
@ -637,7 +642,8 @@ impl Ui {
|
|||
widget_rect
|
||||
}
|
||||
|
||||
/// Allocate a specific part of the ui.
|
||||
/// Allocate a specific part of the `Ui‘.
|
||||
/// Ignore the layout of the `Ui‘: just put my widget here!
|
||||
pub(crate) fn allocate_rect(&mut self, rect: Rect, sense: Sense) -> Response {
|
||||
let id = self.advance_cursor_after_rect(rect);
|
||||
self.interact(rect, id, sense)
|
||||
|
@ -666,7 +672,9 @@ impl Ui {
|
|||
) -> (R, Response) {
|
||||
let item_spacing = self.spacing().item_spacing;
|
||||
let outer_child_rect = self.placer.next_space(desired_size, item_spacing);
|
||||
let inner_child_rect = self.placer.justify_or_align(outer_child_rect, desired_size);
|
||||
let inner_child_rect = self
|
||||
.placer
|
||||
.justify_and_align(outer_child_rect, desired_size);
|
||||
|
||||
let mut child_ui = self.child_ui(inner_child_rect, *self.layout());
|
||||
let ret = add_contents(&mut child_ui);
|
||||
|
@ -682,6 +690,29 @@ impl Ui {
|
|||
(ret, response)
|
||||
}
|
||||
|
||||
/// Allocated the given rectangle and then adds content to that rectangle.
|
||||
/// If the contents overflow, more space will be allocated.
|
||||
/// When finished, the amount of space actually used (`min_rect`) will be allocated.
|
||||
/// So you can request a lot of space and then use less.
|
||||
pub fn allocate_ui_at_rect<R>(
|
||||
&mut self,
|
||||
max_rect: Rect,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> (R, Response) {
|
||||
let mut child_ui = self.child_ui(max_rect, *self.layout());
|
||||
let ret = add_contents(&mut child_ui);
|
||||
let final_child_rect = child_ui.min_rect();
|
||||
|
||||
self.placer.advance_after_rects(
|
||||
final_child_rect,
|
||||
final_child_rect,
|
||||
self.spacing().item_spacing,
|
||||
);
|
||||
|
||||
let response = self.interact(final_child_rect, child_ui.id, Sense::hover());
|
||||
(ret, response)
|
||||
}
|
||||
|
||||
/// Convenience function to get a region to paint on
|
||||
pub fn allocate_painter(&mut self, desired_size: Vec2, sense: Sense) -> (Response, Painter) {
|
||||
let response = self.allocate_response(desired_size, sense);
|
||||
|
@ -729,6 +760,22 @@ impl Ui {
|
|||
widget.ui(self)
|
||||
}
|
||||
|
||||
/// Add a widget to this `Ui` with a given max size.
|
||||
pub fn add_sized(&mut self, max_size: Vec2, widget: impl Widget) -> Response {
|
||||
self.allocate_ui(max_size, |ui| {
|
||||
ui.centered_and_justified(|ui| ui.add(widget)).0
|
||||
})
|
||||
.0
|
||||
}
|
||||
|
||||
/// Add a widget to this `Ui` at a specific location (manual layout).
|
||||
pub fn put(&mut self, max_rect: Rect, widget: impl Widget) -> Response {
|
||||
self.allocate_ui_at_rect(max_rect, |ui| {
|
||||
ui.centered_and_justified(|ui| ui.add(widget)).0
|
||||
})
|
||||
.0
|
||||
}
|
||||
|
||||
/// Shortcut for `add(Label::new(text))`
|
||||
pub fn label(&mut self, label: impl Into<Label>) -> Response {
|
||||
self.add(label.into())
|
||||
|
@ -1256,6 +1303,17 @@ impl Ui {
|
|||
(ret, self.interact(rect, child_ui.id, Sense::hover()))
|
||||
}
|
||||
|
||||
/// This will make the next added widget centered and justified in the available space.
|
||||
pub fn centered_and_justified<R>(
|
||||
&mut self,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> (R, Response) {
|
||||
self.with_layout(
|
||||
Layout::centered_and_justified(Direction::TopDown),
|
||||
add_contents,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn set_grid(&mut self, grid: grid::GridLayout) {
|
||||
self.placer.set_grid(grid);
|
||||
}
|
||||
|
@ -1332,7 +1390,7 @@ impl Ui {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// ## Debug stuff
|
||||
/// # Debug stuff
|
||||
impl Ui {
|
||||
/// Shows where the next widget is going to be placed
|
||||
pub fn debug_paint_cursor(&self) {
|
||||
|
|
|
@ -22,10 +22,11 @@ impl Default for Demos {
|
|||
Box::new(super::widget_gallery::WidgetGallery::default()),
|
||||
Box::new(super::window_options::WindowOptions::default()),
|
||||
// Tests:
|
||||
Box::new(super::layout_test::LayoutTest::default()),
|
||||
Box::new(super::tests::IdTest::default()),
|
||||
Box::new(super::tests::TableTest::default()),
|
||||
Box::new(super::tests::InputTest::default()),
|
||||
Box::new(super::layout_test::LayoutTest::default()),
|
||||
Box::new(super::tests::ManualLayoutTest::default()),
|
||||
Box::new(super::tests::TableTest::default()),
|
||||
];
|
||||
Self {
|
||||
open: vec![false; demos.len()],
|
||||
|
|
|
@ -52,6 +52,91 @@ impl super::View for IdTest {
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum WidgetType {
|
||||
Label,
|
||||
Button,
|
||||
TextEdit,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ManualLayoutTest {
|
||||
widget_offset: egui::Vec2,
|
||||
widget_size: egui::Vec2,
|
||||
widget_type: WidgetType,
|
||||
text_edit_contents: String,
|
||||
}
|
||||
|
||||
impl Default for ManualLayoutTest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
widget_offset: egui::Vec2::splat(150.0),
|
||||
widget_size: egui::Vec2::new(200.0, 100.0),
|
||||
widget_type: WidgetType::Button,
|
||||
text_edit_contents: crate::LOREM_IPSUM.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Demo for ManualLayoutTest {
|
||||
fn name(&self) -> &str {
|
||||
"Manual Layout Test"
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
|
||||
egui::Window::new(self.name()).open(open).show(ctx, |ui| {
|
||||
use super::View;
|
||||
self.ui(ui);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl super::View for ManualLayoutTest {
|
||||
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
use egui::*;
|
||||
reset_button(ui, self);
|
||||
let Self {
|
||||
widget_offset,
|
||||
widget_size,
|
||||
widget_type,
|
||||
text_edit_contents,
|
||||
} = self;
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Test widget:");
|
||||
ui.radio_value(widget_type, WidgetType::Button, "Button");
|
||||
ui.radio_value(widget_type, WidgetType::Label, "Label");
|
||||
ui.radio_value(widget_type, WidgetType::TextEdit, "TextEdit");
|
||||
});
|
||||
Grid::new("pos_size").show(ui, |ui| {
|
||||
ui.label("Widget position:");
|
||||
ui.add(Slider::f32(&mut widget_offset.x, 0.0..=400.0));
|
||||
ui.add(Slider::f32(&mut widget_offset.y, 0.0..=400.0));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Widget size:");
|
||||
ui.add(Slider::f32(&mut widget_size.x, 0.0..=400.0));
|
||||
ui.add(Slider::f32(&mut widget_size.y, 0.0..=400.0));
|
||||
ui.end_row();
|
||||
});
|
||||
let widget_rect = Rect::from_min_size(ui.min_rect().min + *widget_offset, *widget_size);
|
||||
|
||||
// Showing how to place a widget anywhere in the `Ui`:
|
||||
match *widget_type {
|
||||
WidgetType::Button => {
|
||||
ui.put(widget_rect, Button::new("Example button"));
|
||||
}
|
||||
WidgetType::Label => {
|
||||
ui.put(widget_rect, Label::new("Example label"));
|
||||
}
|
||||
WidgetType::TextEdit => {
|
||||
ui.put(widget_rect, TextEdit::multiline(text_edit_contents));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct TableTest {
|
||||
num_cols: usize,
|
||||
num_rows: usize,
|
||||
|
|
|
@ -62,6 +62,8 @@ impl super::View for WidgetGallery {
|
|||
});
|
||||
ui.set_enabled(*enabled);
|
||||
|
||||
ui.separator();
|
||||
|
||||
let grid = egui::Grid::new("my_grid")
|
||||
.striped(true)
|
||||
.spacing([40.0, 4.0]);
|
||||
|
|
Loading…
Reference in a new issue