Grid layout and widget gallery (#106)
* Wrap Layout and Region into a new struct Placer * [egui] Add a simple grid layout * Refactor CollapsingHeader code (simplify header painting) * Fix: allow putting a CollapsingHeader inside of a grid layout * [demo] Add a widget gallery Closes https://github.com/emilk/egui/issues/88 * Add optional striped grid background
This commit is contained in:
parent
d344c9d9a3
commit
0b10fa5c29
12 changed files with 663 additions and 174 deletions
|
@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
### Added ⭐
|
### Added ⭐
|
||||||
|
|
||||||
|
* Add a simple grid layout (`Grid`).
|
||||||
* Add `ui.allocate_at_least` and `ui.allocate_exact_size`.
|
* Add `ui.allocate_at_least` and `ui.allocate_exact_size`.
|
||||||
|
|
||||||
### Changed 🔧
|
### Changed 🔧
|
||||||
|
|
|
@ -203,7 +203,12 @@ impl CollapsingHeader {
|
||||||
state.toggle(ui);
|
state.toggle(ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
let bg_index = ui.painter().add(Shape::Noop);
|
ui.painter().add(Shape::Rect {
|
||||||
|
rect: header_response.rect,
|
||||||
|
corner_radius: ui.style().interact(&header_response).corner_radius,
|
||||||
|
fill: ui.style().interact(&header_response).bg_fill,
|
||||||
|
stroke: Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
let (mut icon_rect, _) = ui.style().spacing.icon_rectangles(header_response.rect);
|
let (mut icon_rect, _) = ui.style().spacing.icon_rectangles(header_response.rect);
|
||||||
|
@ -219,24 +224,13 @@ impl CollapsingHeader {
|
||||||
paint_icon(ui, openness, &icon_response);
|
paint_icon(ui, openness, &icon_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
let painter = ui.painter();
|
ui.painter().galley(
|
||||||
painter.galley(
|
|
||||||
text_pos,
|
text_pos,
|
||||||
galley,
|
galley,
|
||||||
label.text_style_or_default(ui.style()),
|
label.text_style_or_default(ui.style()),
|
||||||
ui.style().interact(&header_response).text_color(),
|
ui.style().interact(&header_response).text_color(),
|
||||||
);
|
);
|
||||||
|
|
||||||
painter.set(
|
|
||||||
bg_index,
|
|
||||||
Shape::Rect {
|
|
||||||
rect: header_response.rect,
|
|
||||||
corner_radius: ui.style().interact(&header_response).corner_radius,
|
|
||||||
fill: ui.style().interact(&header_response).bg_fill,
|
|
||||||
stroke: Default::default(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Prepared {
|
Prepared {
|
||||||
id,
|
id,
|
||||||
header_response,
|
header_response,
|
||||||
|
@ -249,27 +243,32 @@ impl CollapsingHeader {
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> CollapsingResponse<R> {
|
) -> CollapsingResponse<R> {
|
||||||
let Prepared {
|
// Make sure contents are bellow header,
|
||||||
id,
|
// and make sure it is one unit (necessary for putting a `CollapsingHeader` in a grid).
|
||||||
header_response,
|
ui.vertical(|ui| {
|
||||||
mut state,
|
let Prepared {
|
||||||
} = self.begin(ui);
|
id,
|
||||||
let ret_response = state.add_contents(ui, id, |ui| ui.indent(id, add_contents).0);
|
header_response,
|
||||||
ui.memory().collapsing_headers.insert(id, state);
|
mut state,
|
||||||
|
} = self.begin(ui);
|
||||||
|
let ret_response = state.add_contents(ui, id, |ui| ui.indent(id, add_contents).0);
|
||||||
|
ui.memory().collapsing_headers.insert(id, state);
|
||||||
|
|
||||||
if let Some((ret, response)) = ret_response {
|
if let Some((ret, response)) = ret_response {
|
||||||
CollapsingResponse {
|
CollapsingResponse {
|
||||||
header_response,
|
header_response,
|
||||||
body_response: Some(response),
|
body_response: Some(response),
|
||||||
body_returned: Some(ret),
|
body_returned: Some(ret),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CollapsingResponse {
|
||||||
|
header_response,
|
||||||
|
body_response: None,
|
||||||
|
body_returned: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
CollapsingResponse {
|
.0
|
||||||
header_response,
|
|
||||||
body_response: None,
|
|
||||||
body_returned: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
189
egui/src/grid.rs
Normal file
189
egui/src/grid.rs
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub(crate) struct State {
|
||||||
|
col_widths: Vec<f32>,
|
||||||
|
row_heights: Vec<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
/// Returns `true` if this made the column wider.
|
||||||
|
fn set_min_col_width(&mut self, col: usize, width: f32) -> bool {
|
||||||
|
self.col_widths
|
||||||
|
.resize(self.col_widths.len().max(col + 1), 0.0);
|
||||||
|
if self.col_widths[col] < width {
|
||||||
|
self.col_widths[col] = width;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if this made the row higher.
|
||||||
|
fn set_min_row_height(&mut self, row: usize, height: f32) -> bool {
|
||||||
|
self.row_heights
|
||||||
|
.resize(self.row_heights.len().max(row + 1), 0.0);
|
||||||
|
if self.row_heights[row] < height {
|
||||||
|
self.row_heights[row] = height;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn col_width(&self, col: usize) -> Option<f32> {
|
||||||
|
self.col_widths.get(col).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_height(&self, row: usize) -> Option<f32> {
|
||||||
|
self.row_heights.get(row).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn full_width(&self, x_spacing: f32) -> f32 {
|
||||||
|
self.col_widths.iter().sum::<f32>()
|
||||||
|
+ (self.col_widths.len().at_least(1) - 1) as f32 * x_spacing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub(crate) struct GridLayout {
|
||||||
|
ctx: CtxRef,
|
||||||
|
id: Id,
|
||||||
|
state: State,
|
||||||
|
spacing: Vec2,
|
||||||
|
striped: bool,
|
||||||
|
initial_x: f32,
|
||||||
|
default_row_height: f32,
|
||||||
|
col: usize,
|
||||||
|
row: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridLayout {
|
||||||
|
pub(crate) fn new(ui: &Ui, id: Id) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx: ui.ctx().clone(),
|
||||||
|
id,
|
||||||
|
state: ui.memory().grid.get(&id).cloned().unwrap_or_default(),
|
||||||
|
spacing: ui.style().spacing.item_spacing,
|
||||||
|
striped: false,
|
||||||
|
initial_x: ui.cursor().x,
|
||||||
|
default_row_height: 0.0,
|
||||||
|
col: 0,
|
||||||
|
row: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `true`, add a subtle background color to every other row.
|
||||||
|
///
|
||||||
|
/// This can make a table easier to read.
|
||||||
|
/// Default: `false`.
|
||||||
|
pub(crate) fn striped(mut self, striped: bool) -> Self {
|
||||||
|
self.striped = striped;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridLayout {
|
||||||
|
fn row_height(&self, row: usize) -> f32 {
|
||||||
|
self.state
|
||||||
|
.row_height(row)
|
||||||
|
.unwrap_or(self.default_row_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn available_rect(&self, region: &Region) -> Rect {
|
||||||
|
Rect::from_min_max(region.cursor, region.max_rect.max)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn next_cell(&self, cursor: Pos2, child_size: Vec2) -> Rect {
|
||||||
|
let width = self.state.col_width(self.col).unwrap_or(0.0);
|
||||||
|
let height = self.row_height(self.row);
|
||||||
|
let size = child_size.max(vec2(width, height));
|
||||||
|
Rect::from_min_size(cursor, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn advance(&mut self, cursor: &mut Pos2, rect: Rect) {
|
||||||
|
let dirty = self.state.set_min_col_width(self.col, rect.width());
|
||||||
|
let dirty = self.state.set_min_row_height(self.row, rect.height()) || dirty;
|
||||||
|
if dirty {
|
||||||
|
self.ctx.memory().grid.insert(self.id, self.state.clone());
|
||||||
|
self.ctx.request_repaint();
|
||||||
|
}
|
||||||
|
self.col += 1;
|
||||||
|
cursor.x += rect.width() + self.spacing.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn end_row(&mut self, cursor: &mut Pos2, painter: &Painter) {
|
||||||
|
let row_height = self.row_height(self.row);
|
||||||
|
|
||||||
|
cursor.x = self.initial_x;
|
||||||
|
cursor.y += row_height + self.spacing.y;
|
||||||
|
self.col = 0;
|
||||||
|
self.row += 1;
|
||||||
|
|
||||||
|
if self.striped && self.row % 2 == 1 {
|
||||||
|
if let Some(height) = self.state.row_height(self.row) {
|
||||||
|
// Paint background for coming row:
|
||||||
|
let size = Vec2::new(self.state.full_width(self.spacing.x), height);
|
||||||
|
let rect = Rect::from_min_size(*cursor, size);
|
||||||
|
let color = Rgba::from_white_alpha(0.0075);
|
||||||
|
// let color = Rgba::from_black_alpha(0.2);
|
||||||
|
painter.rect_filled(rect, 2.0, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A simple `Grid` layout.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # let ui = &mut egui::Ui::__test();
|
||||||
|
/// egui::Grid::new("some_unique_id").show(ui, |ui| {
|
||||||
|
/// ui.label("First row, first column");
|
||||||
|
/// ui.label("First row, second column");
|
||||||
|
/// ui.end_row();
|
||||||
|
///
|
||||||
|
/// ui.label("Second row, first column");
|
||||||
|
/// ui.label("Second row, second column");
|
||||||
|
/// ui.label("Second row, third column");
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub struct Grid {
|
||||||
|
id_source: Id,
|
||||||
|
striped: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
pub fn new(id_source: impl std::hash::Hash) -> Self {
|
||||||
|
Self {
|
||||||
|
id_source: Id::new(id_source),
|
||||||
|
striped: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If `true`, add a subtle background color to every other row.
|
||||||
|
///
|
||||||
|
/// This can make a table easier to read.
|
||||||
|
/// Default: `false`.
|
||||||
|
pub fn striped(mut self, striped: bool) -> Self {
|
||||||
|
self.striped = striped;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Grid {
|
||||||
|
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
|
||||||
|
let Self { id_source, striped } = self;
|
||||||
|
|
||||||
|
ui.wrap(|ui| {
|
||||||
|
let id = ui.make_persistent_id(id_source);
|
||||||
|
let grid = GridLayout::new(ui, id).striped(striped);
|
||||||
|
ui.set_grid(grid);
|
||||||
|
add_contents(ui)
|
||||||
|
})
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
}
|
|
@ -431,33 +431,17 @@ impl Layout {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply justify or alignment after calling `next_space`.
|
/// Apply justify or alignment after calling `next_space`.
|
||||||
pub(crate) fn justify_or_align(&self, mut rect: Rect, child_size: Vec2) -> Rect {
|
pub(crate) fn justify_or_align(&self, rect: Rect, mut child_size: Vec2) -> Rect {
|
||||||
if self.main_dir.is_horizontal() {
|
if self.main_dir.is_horizontal() {
|
||||||
debug_assert!((rect.width() - child_size.x).abs() < 0.1);
|
|
||||||
if self.cross_justify {
|
if self.cross_justify {
|
||||||
rect // fill full height
|
child_size.y = rect.height(); // fill full height
|
||||||
} else {
|
|
||||||
rect.min.y += match self.cross_align {
|
|
||||||
Align::Min => 0.0,
|
|
||||||
Align::Center => 0.5 * (rect.size().y - child_size.y),
|
|
||||||
Align::Max => rect.size().y - child_size.y,
|
|
||||||
};
|
|
||||||
rect.max.y = rect.min.y + child_size.y;
|
|
||||||
rect
|
|
||||||
}
|
}
|
||||||
|
Align2([Align::Center, self.cross_align]).align_size_within_rect(child_size, rect)
|
||||||
} else {
|
} else {
|
||||||
debug_assert!((rect.height() - child_size.y).abs() < 0.1);
|
|
||||||
if self.cross_justify {
|
if self.cross_justify {
|
||||||
rect // justified: fill full width
|
child_size.x = rect.width(); // fill full width
|
||||||
} else {
|
|
||||||
rect.min.x += match self.cross_align {
|
|
||||||
Align::Min => 0.0,
|
|
||||||
Align::Center => 0.5 * (rect.size().x - child_size.x),
|
|
||||||
Align::Max => rect.size().x - child_size.x,
|
|
||||||
};
|
|
||||||
rect.max.x = rect.min.x + child_size.x;
|
|
||||||
rect
|
|
||||||
}
|
}
|
||||||
|
Align2([self.cross_align, Align::Center]).align_size_within_rect(child_size, rect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,6 +472,18 @@ impl Layout {
|
||||||
Direction::BottomUp => pos2(outer_rect.left(), inner_rect.top() - item_spacing.y),
|
Direction::BottomUp => pos2(outer_rect.left(), inner_rect.top() - item_spacing.y),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Move to the next row in a wrapping layout.
|
||||||
|
/// Otherwise does nothing.
|
||||||
|
pub(crate) fn end_row(&mut self, region: &mut Region, item_spacing: Vec2) {
|
||||||
|
if self.main_wrap && self.is_horizontal() {
|
||||||
|
// New row
|
||||||
|
region.cursor = pos2(
|
||||||
|
region.max_rect.left(),
|
||||||
|
region.max_rect.bottom() + item_spacing.y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -495,14 +491,16 @@ impl Layout {
|
||||||
/// ## Debug stuff
|
/// ## Debug stuff
|
||||||
impl Layout {
|
impl Layout {
|
||||||
/// Shows where the next widget is going to be placed
|
/// Shows where the next widget is going to be placed
|
||||||
pub(crate) fn debug_paint_cursor(&self, region: &Region, painter: &crate::Painter) {
|
pub(crate) fn debug_paint_cursor(
|
||||||
|
&self,
|
||||||
|
region: &Region,
|
||||||
|
stroke: epaint::Stroke,
|
||||||
|
painter: &crate::Painter,
|
||||||
|
) {
|
||||||
use crate::paint::*;
|
use crate::paint::*;
|
||||||
|
|
||||||
let cursor = region.cursor;
|
let cursor = region.cursor;
|
||||||
|
|
||||||
let color = Color32::GREEN;
|
|
||||||
let stroke = Stroke::new(2.0, color);
|
|
||||||
|
|
||||||
let align;
|
let align;
|
||||||
|
|
||||||
let l = 64.0;
|
let l = 64.0;
|
||||||
|
@ -526,6 +524,6 @@ impl Layout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.text(cursor, align, "cursor", TextStyle::Monospace, color);
|
painter.text(cursor, align, "cursor", TextStyle::Monospace, stroke.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
mod animation_manager;
|
mod animation_manager;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
mod context;
|
mod context;
|
||||||
|
pub(crate) mod grid;
|
||||||
mod id;
|
mod id;
|
||||||
mod input;
|
mod input;
|
||||||
mod introspection;
|
mod introspection;
|
||||||
|
@ -89,6 +90,7 @@ mod layout;
|
||||||
mod memory;
|
mod memory;
|
||||||
pub mod menu;
|
pub mod menu;
|
||||||
mod painter;
|
mod painter;
|
||||||
|
pub(crate) mod placer;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
mod types;
|
mod types;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
@ -111,6 +113,7 @@ pub use epaint::{
|
||||||
pub use {
|
pub use {
|
||||||
containers::*,
|
containers::*,
|
||||||
context::{Context, CtxRef},
|
context::{Context, CtxRef},
|
||||||
|
grid::Grid,
|
||||||
id::Id,
|
id::Id,
|
||||||
input::*,
|
input::*,
|
||||||
layers::*,
|
layers::*,
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub struct Memory {
|
||||||
|
|
||||||
// states of various types of widgets
|
// states of various types of widgets
|
||||||
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
pub(crate) collapsing_headers: HashMap<Id, collapsing_header::State>,
|
||||||
|
pub(crate) grid: HashMap<Id, crate::grid::State>,
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
pub(crate) menu_bar: HashMap<Id, menu::BarState>,
|
pub(crate) menu_bar: HashMap<Id, menu::BarState>,
|
||||||
pub(crate) resize: HashMap<Id, resize::State>,
|
pub(crate) resize: HashMap<Id, resize::State>,
|
||||||
|
|
226
egui/src/placer.rs
Normal file
226
egui/src/placer.rs
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
pub(crate) struct Placer {
|
||||||
|
/// If set this will take precedence over [`layout`].
|
||||||
|
grid: Option<grid::GridLayout>,
|
||||||
|
layout: Layout,
|
||||||
|
region: Region,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Placer {
|
||||||
|
pub(crate) fn new(max_rect: Rect, layout: Layout) -> Self {
|
||||||
|
let region = layout.region_from_max_rect(max_rect);
|
||||||
|
Self {
|
||||||
|
grid: None,
|
||||||
|
layout,
|
||||||
|
region,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_grid(&mut self, grid: grid::GridLayout) {
|
||||||
|
self.grid = Some(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn layout(&self) -> &Layout {
|
||||||
|
&self.layout
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn prefer_right_to_left(&self) -> bool {
|
||||||
|
self.layout.prefer_right_to_left()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn min_rect(&self) -> Rect {
|
||||||
|
self.region.min_rect
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn max_rect(&self) -> Rect {
|
||||||
|
self.region.max_rect
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn max_rect_finite(&self) -> Rect {
|
||||||
|
self.region.max_rect_finite()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
|
||||||
|
self.region.min_rect = min_rect;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cursor(&self) -> Pos2 {
|
||||||
|
self.region.cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Placer {
|
||||||
|
pub(crate) fn align_size_within_rect(&self, size: Vec2, outer: Rect) -> Rect {
|
||||||
|
self.layout.align_size_within_rect(size, outer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn available_rect_before_wrap(&self) -> Rect {
|
||||||
|
if let Some(grid) = &self.grid {
|
||||||
|
grid.available_rect(&self.region)
|
||||||
|
} else {
|
||||||
|
self.layout.available_rect_before_wrap(&self.region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn available_rect_before_wrap_finite(&self) -> Rect {
|
||||||
|
if let Some(grid) = &self.grid {
|
||||||
|
grid.available_rect(&self.region)
|
||||||
|
} else {
|
||||||
|
self.layout.available_rect_before_wrap_finite(&self.region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Amount of space available for a widget.
|
||||||
|
/// For wrapping layouts, this is the maximum (after wrap).
|
||||||
|
pub(crate) fn available_size(&self) -> Vec2 {
|
||||||
|
if let Some(grid) = &self.grid {
|
||||||
|
grid.available_rect(&self.region).size()
|
||||||
|
} else {
|
||||||
|
self.layout.available_size(&self.region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns where to put the next widget that is of the given size.
|
||||||
|
/// The returned "outer" `Rect` will always be justified along the cross axis.
|
||||||
|
/// This is what you then pass to `advance_after_outer_rect`.
|
||||||
|
/// Use `justify_or_align` to get the inner `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)
|
||||||
|
} else {
|
||||||
|
self.layout
|
||||||
|
.next_space(&self.region, child_size, item_spacing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply justify or alignment after calling `next_space`.
|
||||||
|
pub(crate) fn justify_or_align(&self, rect: Rect, child_size: Vec2) -> Rect {
|
||||||
|
self.layout.justify_or_align(rect, child_size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the cursor by this many points.
|
||||||
|
pub(crate) fn advance_cursor(&mut self, amount: f32) {
|
||||||
|
debug_assert!(
|
||||||
|
self.grid.is_none(),
|
||||||
|
"You cannot advance the cursor when in a grid layout"
|
||||||
|
);
|
||||||
|
self.layout.advance_cursor(&mut self.region, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance cursor after a widget was added to a specific rectangle.
|
||||||
|
/// `outer_rect` is a hack needed because the Vec2 cursor is not quite sufficient to keep track
|
||||||
|
/// of what is happening when we are doing wrapping layouts.
|
||||||
|
pub(crate) fn advance_after_outer_rect(
|
||||||
|
&mut self,
|
||||||
|
outer_rect: Rect,
|
||||||
|
inner_rect: Rect,
|
||||||
|
item_spacing: Vec2,
|
||||||
|
) {
|
||||||
|
if let Some(grid) = &mut self.grid {
|
||||||
|
grid.advance(&mut self.region.cursor, outer_rect)
|
||||||
|
} else {
|
||||||
|
self.layout.advance_after_outer_rect(
|
||||||
|
&mut self.region,
|
||||||
|
outer_rect,
|
||||||
|
inner_rect,
|
||||||
|
item_spacing,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move to the next row in a grid layout or wrapping layout.
|
||||||
|
/// Otherwise does nothing.
|
||||||
|
pub(crate) fn end_row(&mut self, item_spacing: Vec2, painter: &Painter) {
|
||||||
|
if let Some(grid) = &mut self.grid {
|
||||||
|
grid.end_row(&mut self.region.cursor, painter)
|
||||||
|
} else {
|
||||||
|
self.layout.end_row(&mut self.region, item_spacing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Placer {
|
||||||
|
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
|
||||||
|
pub(crate) fn expand_to_include_rect(&mut self, rect: Rect) {
|
||||||
|
self.region.expand_to_include_rect(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the maximum width of the ui.
|
||||||
|
/// You won't be able to shrink it below the current minimum size.
|
||||||
|
pub(crate) fn set_max_width(&mut self, width: f32) {
|
||||||
|
#![allow(clippy::float_cmp)]
|
||||||
|
let Self { layout, region, .. } = self;
|
||||||
|
if layout.main_dir() == Direction::RightToLeft {
|
||||||
|
debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x);
|
||||||
|
region.max_rect.min.x = region.max_rect.max.x - width.at_least(region.min_rect.width());
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x);
|
||||||
|
region.max_rect.max.x = region.max_rect.min.x + width.at_least(region.min_rect.width());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the maximum height of the ui.
|
||||||
|
/// You won't be able to shrink it below the current minimum size.
|
||||||
|
pub(crate) fn set_max_height(&mut self, height: f32) {
|
||||||
|
#![allow(clippy::float_cmp)]
|
||||||
|
let Self { layout, region, .. } = self;
|
||||||
|
if layout.main_dir() == Direction::BottomUp {
|
||||||
|
debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y);
|
||||||
|
region.max_rect.min.y =
|
||||||
|
region.max_rect.max.y - height.at_least(region.min_rect.height());
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y);
|
||||||
|
region.max_rect.max.y =
|
||||||
|
region.max_rect.min.y + height.at_least(region.min_rect.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the minimum width of the ui.
|
||||||
|
/// This can't shrink the ui, only make it larger.
|
||||||
|
pub(crate) fn set_min_width(&mut self, width: f32) {
|
||||||
|
#![allow(clippy::float_cmp)]
|
||||||
|
let Self { layout, region, .. } = self;
|
||||||
|
if layout.main_dir() == Direction::RightToLeft {
|
||||||
|
debug_assert_eq!(region.min_rect.max.x, region.max_rect.max.x);
|
||||||
|
let min_rect = &mut region.min_rect;
|
||||||
|
min_rect.min.x = min_rect.min.x.min(min_rect.max.x - width);
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(region.min_rect.min.x, region.max_rect.min.x);
|
||||||
|
let min_rect = &mut region.min_rect;
|
||||||
|
min_rect.max.x = min_rect.max.x.max(min_rect.min.x + width);
|
||||||
|
}
|
||||||
|
region.max_rect = region.max_rect.union(region.min_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the minimum height of the ui.
|
||||||
|
/// This can't shrink the ui, only make it larger.
|
||||||
|
pub(crate) fn set_min_height(&mut self, height: f32) {
|
||||||
|
#![allow(clippy::float_cmp)]
|
||||||
|
let Self { layout, region, .. } = self;
|
||||||
|
if layout.main_dir() == Direction::BottomUp {
|
||||||
|
debug_assert_eq!(region.min_rect.max.y, region.max_rect.max.y);
|
||||||
|
let min_rect = &mut region.min_rect;
|
||||||
|
min_rect.min.y = min_rect.min.y.min(min_rect.max.y - height);
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(region.min_rect.min.y, region.max_rect.min.y);
|
||||||
|
let min_rect = &mut region.min_rect;
|
||||||
|
min_rect.max.y = min_rect.max.y.max(min_rect.min.y + height);
|
||||||
|
}
|
||||||
|
region.max_rect = region.max_rect.union(region.min_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Placer {
|
||||||
|
pub(crate) fn debug_paint_cursor(&self, painter: &crate::Painter) {
|
||||||
|
let color = Color32::GREEN;
|
||||||
|
let stroke = Stroke::new(2.0, color);
|
||||||
|
|
||||||
|
if let Some(grid) = &self.grid {
|
||||||
|
painter.rect_stroke(grid.next_cell(self.cursor(), Vec2::splat(0.0)), 1.0, stroke)
|
||||||
|
} else {
|
||||||
|
self.layout
|
||||||
|
.debug_paint_cursor(&self.region, stroke, painter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
egui/src/ui.rs
181
egui/src/ui.rs
|
@ -3,7 +3,8 @@
|
||||||
use std::{hash::Hash, sync::Arc};
|
use std::{hash::Hash, sync::Arc};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::*, containers::*, layout::*, mutex::MutexGuard, paint::text::Fonts, widgets::*, *,
|
color::*, containers::*, layout::*, mutex::MutexGuard, paint::text::Fonts, placer::Placer,
|
||||||
|
widgets::*, *,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This is what you use to place widgets.
|
/// This is what you use to place widgets.
|
||||||
|
@ -43,11 +44,8 @@ pub struct Ui {
|
||||||
/// The `Ui` implements copy-on-write for this.
|
/// The `Ui` implements copy-on-write for this.
|
||||||
style: Arc<Style>,
|
style: Arc<Style>,
|
||||||
|
|
||||||
/// The strategy for where to put the next widget.
|
/// Handles the `Ui` size and the placement of new widgets.
|
||||||
layout: Layout,
|
placer: Placer,
|
||||||
|
|
||||||
/// Sizes/bounds and cursor used by `Layout`.
|
|
||||||
region: Region,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
|
@ -56,29 +54,24 @@ impl Ui {
|
||||||
|
|
||||||
pub fn new(ctx: CtxRef, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
pub fn new(ctx: CtxRef, layer_id: LayerId, id: Id, max_rect: Rect, clip_rect: Rect) -> Self {
|
||||||
let style = ctx.style();
|
let style = ctx.style();
|
||||||
let layout = Layout::default();
|
|
||||||
let region = layout.region_from_max_rect(max_rect);
|
|
||||||
Ui {
|
Ui {
|
||||||
id,
|
id,
|
||||||
next_auto_id: id.with("auto").value(),
|
next_auto_id: id.with("auto").value(),
|
||||||
painter: Painter::new(ctx, layer_id, clip_rect),
|
painter: Painter::new(ctx, layer_id, clip_rect),
|
||||||
style,
|
style,
|
||||||
layout,
|
placer: Placer::new(max_rect, Layout::default()),
|
||||||
region,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
|
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
|
||||||
self.next_auto_id = self.next_auto_id.wrapping_add(1);
|
self.next_auto_id = self.next_auto_id.wrapping_add(1);
|
||||||
let region = layout.region_from_max_rect(max_rect);
|
|
||||||
|
|
||||||
Ui {
|
Ui {
|
||||||
id: self.id.with("child"),
|
id: self.id.with("child"),
|
||||||
next_auto_id: Id::new(self.next_auto_id).with("child").value(),
|
next_auto_id: Id::new(self.next_auto_id).with("child").value(),
|
||||||
painter: self.painter.clone(),
|
painter: self.painter.clone(),
|
||||||
style: self.style.clone(),
|
style: self.style.clone(),
|
||||||
layout,
|
placer: Placer::new(max_rect, layout),
|
||||||
region,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +119,7 @@ impl Ui {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout(&self) -> &Layout {
|
pub fn layout(&self) -> &Layout {
|
||||||
&self.layout
|
self.placer.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a painter for a sub-region of this Ui.
|
/// Create a painter for a sub-region of this Ui.
|
||||||
|
@ -190,7 +183,7 @@ impl Ui {
|
||||||
///
|
///
|
||||||
/// This will grow as new widgets are added, but never shrink.
|
/// This will grow as new widgets are added, but never shrink.
|
||||||
pub fn min_rect(&self) -> Rect {
|
pub fn min_rect(&self) -> Rect {
|
||||||
self.region.min_rect
|
self.placer.min_rect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Size of content; same as `min_rect().size()`
|
/// Size of content; same as `min_rect().size()`
|
||||||
|
@ -206,19 +199,19 @@ impl Ui {
|
||||||
/// If a new widget doesn't fit within the `max_rect` then the
|
/// If a new widget doesn't fit within the `max_rect` then the
|
||||||
/// `Ui` will make room for it by expanding both `min_rect` and `max_rect`.
|
/// `Ui` will make room for it by expanding both `min_rect` and `max_rect`.
|
||||||
pub fn max_rect(&self) -> Rect {
|
pub fn max_rect(&self) -> Rect {
|
||||||
self.region.max_rect
|
self.placer.max_rect()
|
||||||
}
|
|
||||||
|
|
||||||
/// Used for animation, kind of hacky
|
|
||||||
pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
|
|
||||||
self.region.min_rect = min_rect;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is like `max_rect()`, but will never be infinite.
|
/// This is like `max_rect()`, but will never be infinite.
|
||||||
/// If the desired rect is infinite ("be as big as you want")
|
/// If the desired rect is infinite ("be as big as you want")
|
||||||
/// this will be bounded by `min_rect` instead.
|
/// this will be bounded by `min_rect` instead.
|
||||||
pub fn max_rect_finite(&self) -> Rect {
|
pub fn max_rect_finite(&self) -> Rect {
|
||||||
self.region.max_rect_finite()
|
self.placer.max_rect_finite()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used for animation, kind of hacky
|
||||||
|
pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
|
||||||
|
self.placer.force_set_min_rect(min_rect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -233,31 +226,13 @@ impl Ui {
|
||||||
/// Set the maximum width of the ui.
|
/// Set the maximum width of the ui.
|
||||||
/// You won't be able to shrink it below the current minimum size.
|
/// You won't be able to shrink it below the current minimum size.
|
||||||
pub fn set_max_width(&mut self, width: f32) {
|
pub fn set_max_width(&mut self, width: f32) {
|
||||||
#![allow(clippy::float_cmp)]
|
self.placer.set_max_width(width);
|
||||||
if self.layout.main_dir() == Direction::RightToLeft {
|
|
||||||
debug_assert_eq!(self.min_rect().max.x, self.max_rect().max.x);
|
|
||||||
self.region.max_rect.min.x =
|
|
||||||
self.region.max_rect.max.x - width.at_least(self.min_rect().width());
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(self.min_rect().min.x, self.region.max_rect.min.x);
|
|
||||||
self.region.max_rect.max.x =
|
|
||||||
self.region.max_rect.min.x + width.at_least(self.min_rect().width());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the maximum height of the ui.
|
/// Set the maximum height of the ui.
|
||||||
/// You won't be able to shrink it below the current minimum size.
|
/// You won't be able to shrink it below the current minimum size.
|
||||||
pub fn set_max_height(&mut self, height: f32) {
|
pub fn set_max_height(&mut self, height: f32) {
|
||||||
#![allow(clippy::float_cmp)]
|
self.placer.set_max_height(height);
|
||||||
if self.layout.main_dir() == Direction::BottomUp {
|
|
||||||
debug_assert_eq!(self.min_rect().max.y, self.region.max_rect.max.y);
|
|
||||||
self.region.max_rect.min.y =
|
|
||||||
self.region.max_rect.max.y - height.at_least(self.min_rect().height());
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(self.min_rect().min.y, self.region.max_rect.min.y);
|
|
||||||
self.region.max_rect.max.y =
|
|
||||||
self.region.max_rect.min.y + height.at_least(self.min_rect().height());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -272,33 +247,13 @@ impl Ui {
|
||||||
/// Set the minimum width of the ui.
|
/// Set the minimum width of the ui.
|
||||||
/// This can't shrink the ui, only make it larger.
|
/// This can't shrink the ui, only make it larger.
|
||||||
pub fn set_min_width(&mut self, width: f32) {
|
pub fn set_min_width(&mut self, width: f32) {
|
||||||
#![allow(clippy::float_cmp)]
|
self.placer.set_min_width(width);
|
||||||
if self.layout.main_dir() == Direction::RightToLeft {
|
|
||||||
debug_assert_eq!(self.region.min_rect.max.x, self.region.max_rect.max.x);
|
|
||||||
let min_rect = &mut self.region.min_rect;
|
|
||||||
min_rect.min.x = min_rect.min.x.min(min_rect.max.x - width);
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(self.region.min_rect.min.x, self.region.max_rect.min.x);
|
|
||||||
let min_rect = &mut self.region.min_rect;
|
|
||||||
min_rect.max.x = min_rect.max.x.max(min_rect.min.x + width);
|
|
||||||
}
|
|
||||||
self.region.max_rect = self.region.max_rect.union(self.min_rect());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the minimum height of the ui.
|
/// Set the minimum height of the ui.
|
||||||
/// This can't shrink the ui, only make it larger.
|
/// This can't shrink the ui, only make it larger.
|
||||||
pub fn set_min_height(&mut self, height: f32) {
|
pub fn set_min_height(&mut self, height: f32) {
|
||||||
#![allow(clippy::float_cmp)]
|
self.placer.set_min_height(height);
|
||||||
if self.layout.main_dir() == Direction::BottomUp {
|
|
||||||
debug_assert_eq!(self.region.min_rect.max.y, self.region.max_rect.max.y);
|
|
||||||
let min_rect = &mut self.region.min_rect;
|
|
||||||
min_rect.min.y = min_rect.min.y.min(min_rect.max.y - height);
|
|
||||||
} else {
|
|
||||||
debug_assert_eq!(self.region.min_rect.min.y, self.region.max_rect.min.y);
|
|
||||||
let min_rect = &mut self.region.min_rect;
|
|
||||||
min_rect.max.y = min_rect.max.y.max(min_rect.min.y + height);
|
|
||||||
}
|
|
||||||
self.region.max_rect = self.region.max_rect.union(self.min_rect());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -318,7 +273,7 @@ impl Ui {
|
||||||
|
|
||||||
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
|
/// Expand the `min_rect` and `max_rect` of this ui to include a child at the given rect.
|
||||||
pub fn expand_to_include_rect(&mut self, rect: Rect) {
|
pub fn expand_to_include_rect(&mut self, rect: Rect) {
|
||||||
self.region.expand_to_include_rect(rect);
|
self.placer.expand_to_include_rect(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `ui.set_width_range(min..=max);` is equivalent to `ui.set_min_width(min); ui.set_max_width(max);`.
|
/// `ui.set_width_range(min..=max);` is equivalent to `ui.set_min_width(min); ui.set_max_width(max);`.
|
||||||
|
@ -342,7 +297,7 @@ impl Ui {
|
||||||
/// A small size should be interpreted as "as little as possible".
|
/// A small size should be interpreted as "as little as possible".
|
||||||
/// An infinite size should be interpreted as "as much as you want".
|
/// An infinite size should be interpreted as "as much as you want".
|
||||||
pub fn available_size(&self) -> Vec2 {
|
pub fn available_size(&self) -> Vec2 {
|
||||||
self.layout.available_size(&self.region)
|
self.placer.available_size()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available_width(&self) -> f32 {
|
pub fn available_width(&self) -> f32 {
|
||||||
|
@ -351,27 +306,25 @@ impl Ui {
|
||||||
|
|
||||||
/// In case of a wrapping layout, how much space is left on this row/column?
|
/// In case of a wrapping layout, how much space is left on this row/column?
|
||||||
pub fn available_size_before_wrap(&self) -> Vec2 {
|
pub fn available_size_before_wrap(&self) -> Vec2 {
|
||||||
self.layout.available_rect_before_wrap(&self.region).size()
|
self.placer.available_rect_before_wrap().size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is like `available_size_before_wrap()`, but will never be infinite.
|
/// This is like `available_size_before_wrap()`, but will never be infinite.
|
||||||
/// Use this for components that want to grow without bounds (but shouldn't).
|
/// Use this for components that want to grow without bounds (but shouldn't).
|
||||||
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
|
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
|
||||||
pub fn available_size_before_wrap_finite(&self) -> Vec2 {
|
pub fn available_size_before_wrap_finite(&self) -> Vec2 {
|
||||||
self.layout
|
self.placer.available_rect_before_wrap_finite().size()
|
||||||
.available_rect_before_wrap_finite(&self.region)
|
|
||||||
.size()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available_rect_before_wrap(&self) -> Rect {
|
pub fn available_rect_before_wrap(&self) -> Rect {
|
||||||
self.layout.available_rect_before_wrap(&self.region)
|
self.placer.available_rect_before_wrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is like `available_rect_before_wrap()`, but will never be infinite.
|
/// This is like `available_rect_before_wrap()`, but will never be infinite.
|
||||||
/// Use this for components that want to grow without bounds (but shouldn't).
|
/// Use this for components that want to grow without bounds (but shouldn't).
|
||||||
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
|
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
|
||||||
pub fn available_rect_before_wrap_finite(&self) -> Rect {
|
pub fn available_rect_before_wrap_finite(&self) -> Rect {
|
||||||
self.layout.available_rect_before_wrap_finite(&self.region)
|
self.placer.available_rect_before_wrap_finite()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,7 +392,7 @@ impl Ui {
|
||||||
/// The direction is dependent on the layout.
|
/// The direction is dependent on the layout.
|
||||||
/// This is useful for creating some extra space between widgets.
|
/// This is useful for creating some extra space between widgets.
|
||||||
pub fn advance_cursor(&mut self, amount: f32) {
|
pub fn advance_cursor(&mut self, amount: f32) {
|
||||||
self.layout.advance_cursor(&mut self.region, amount);
|
self.placer.advance_cursor(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate space for a widget and check for interaction in the space.
|
/// Allocate space for a widget and check for interaction in the space.
|
||||||
|
@ -474,7 +427,7 @@ impl Ui {
|
||||||
pub fn allocate_exact_size(&mut self, desired_size: Vec2, sense: Sense) -> (Rect, Response) {
|
pub fn allocate_exact_size(&mut self, desired_size: Vec2, sense: Sense) -> (Rect, Response) {
|
||||||
let response = self.allocate_response(desired_size, sense);
|
let response = self.allocate_response(desired_size, sense);
|
||||||
let rect = self
|
let rect = self
|
||||||
.layout()
|
.placer
|
||||||
.align_size_within_rect(desired_size, response.rect);
|
.align_size_within_rect(desired_size, response.rect);
|
||||||
(rect, response)
|
(rect, response)
|
||||||
}
|
}
|
||||||
|
@ -554,34 +507,28 @@ impl Ui {
|
||||||
/// Returns where to put the widget.
|
/// Returns where to put the widget.
|
||||||
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
|
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
|
||||||
let item_spacing = self.style().spacing.item_spacing;
|
let item_spacing = self.style().spacing.item_spacing;
|
||||||
let outer_child_rect = self
|
let outer_child_rect = self.placer.next_space(desired_size, item_spacing);
|
||||||
.layout
|
let inner_child_rect = self.placer.justify_or_align(outer_child_rect, desired_size);
|
||||||
.next_space(&self.region, desired_size, item_spacing);
|
|
||||||
let inner_child_rect = self.layout.justify_or_align(outer_child_rect, desired_size);
|
|
||||||
|
|
||||||
self.layout.advance_after_outer_rect(
|
self.placer
|
||||||
&mut self.region,
|
.advance_after_outer_rect(outer_child_rect, inner_child_rect, item_spacing);
|
||||||
outer_child_rect,
|
self.expand_to_include_rect(inner_child_rect);
|
||||||
inner_child_rect,
|
|
||||||
item_spacing,
|
|
||||||
);
|
|
||||||
self.region.expand_to_include_rect(inner_child_rect);
|
|
||||||
|
|
||||||
inner_child_rect
|
inner_child_rect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id {
|
pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) -> Id {
|
||||||
let item_spacing = self.style().spacing.item_spacing;
|
let item_spacing = self.style().spacing.item_spacing;
|
||||||
self.layout
|
self.placer
|
||||||
.advance_after_outer_rect(&mut self.region, rect, rect, item_spacing);
|
.advance_after_outer_rect(rect, rect, item_spacing);
|
||||||
self.region.expand_to_include_rect(rect);
|
self.expand_to_include_rect(rect);
|
||||||
|
|
||||||
self.next_auto_id = self.next_auto_id.wrapping_add(1);
|
self.next_auto_id = self.next_auto_id.wrapping_add(1);
|
||||||
Id::new(self.next_auto_id)
|
Id::new(self.next_auto_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cursor(&self) -> Pos2 {
|
pub(crate) fn cursor(&self) -> Pos2 {
|
||||||
self.region.cursor
|
self.placer.cursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocated the given space and then adds content to that space.
|
/// Allocated the given space and then adds content to that space.
|
||||||
|
@ -594,22 +541,19 @@ impl Ui {
|
||||||
add_contents: impl FnOnce(&mut Self) -> R,
|
add_contents: impl FnOnce(&mut Self) -> R,
|
||||||
) -> (R, Response) {
|
) -> (R, Response) {
|
||||||
let item_spacing = self.style().spacing.item_spacing;
|
let item_spacing = self.style().spacing.item_spacing;
|
||||||
let outer_child_rect = self
|
let outer_child_rect = self.placer.next_space(desired_size, item_spacing);
|
||||||
.layout
|
let inner_child_rect = self.placer.justify_or_align(outer_child_rect, desired_size);
|
||||||
.next_space(&self.region, desired_size, item_spacing);
|
|
||||||
let inner_child_rect = self.layout.justify_or_align(outer_child_rect, desired_size);
|
|
||||||
|
|
||||||
let mut child_ui = self.child_ui(inner_child_rect, self.layout);
|
let mut child_ui = self.child_ui(inner_child_rect, *self.layout());
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let final_child_rect = child_ui.region.min_rect;
|
let final_child_rect = child_ui.min_rect();
|
||||||
|
|
||||||
self.layout.advance_after_outer_rect(
|
self.placer.advance_after_outer_rect(
|
||||||
&mut self.region,
|
|
||||||
outer_child_rect.union(final_child_rect),
|
outer_child_rect.union(final_child_rect),
|
||||||
final_child_rect,
|
final_child_rect,
|
||||||
item_spacing,
|
item_spacing,
|
||||||
);
|
);
|
||||||
self.region.expand_to_include_rect(final_child_rect);
|
self.expand_to_include_rect(final_child_rect);
|
||||||
|
|
||||||
let response = self.interact(final_child_rect, child_ui.id, Sense::hover());
|
let response = self.interact(final_child_rect, child_ui.id, Sense::hover());
|
||||||
(ret, response)
|
(ret, response)
|
||||||
|
@ -640,8 +584,7 @@ impl Ui {
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub fn scroll_to_cursor(&mut self, align: Align) {
|
pub fn scroll_to_cursor(&mut self, align: Align) {
|
||||||
let scroll_y = self.region.cursor.y;
|
let scroll_y = self.cursor().y;
|
||||||
|
|
||||||
self.ctx().frame_state().scroll_target = Some((scroll_y, align));
|
self.ctx().frame_state().scroll_target = Some((scroll_y, align));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -918,7 +861,7 @@ impl Ui {
|
||||||
/// Create a child ui. You can use this to temporarily change the Style of a sub-region, for instance.
|
/// Create a child ui. You can use this to temporarily change the Style of a sub-region, for instance.
|
||||||
pub fn wrap<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
|
pub fn wrap<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
|
||||||
let child_rect = self.available_rect_before_wrap();
|
let child_rect = self.available_rect_before_wrap();
|
||||||
let mut child_ui = self.child_ui(child_rect, self.layout);
|
let mut child_ui = self.child_ui(child_rect, *self.layout());
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let size = child_ui.min_size();
|
let size = child_ui.min_size();
|
||||||
let response = self.allocate_response(size, Sense::hover());
|
let response = self.allocate_response(size, Sense::hover());
|
||||||
|
@ -962,16 +905,15 @@ impl Ui {
|
||||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||||
) -> (R, Response) {
|
) -> (R, Response) {
|
||||||
assert!(
|
assert!(
|
||||||
self.layout.is_vertical(),
|
self.layout().is_vertical(),
|
||||||
"You can only indent vertical layouts, found {:?}",
|
"You can only indent vertical layouts, found {:?}",
|
||||||
self.layout
|
self.layout()
|
||||||
);
|
);
|
||||||
let indent = vec2(self.style().spacing.indent, 0.0);
|
let indent = vec2(self.style().spacing.indent, 0.0);
|
||||||
let child_rect =
|
let child_rect = Rect::from_min_max(self.cursor() + indent, self.max_rect().right_bottom()); // TODO: wrong for reversed layouts
|
||||||
Rect::from_min_max(self.region.cursor + indent, self.max_rect().right_bottom()); // TODO: wrong for reversed layouts
|
|
||||||
let mut child_ui = Self {
|
let mut child_ui = Self {
|
||||||
id: self.id.with(id_source),
|
id: self.id.with(id_source),
|
||||||
..self.child_ui(child_rect, self.layout)
|
..self.child_ui(child_rect, *self.layout())
|
||||||
};
|
};
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let size = child_ui.min_size();
|
let size = child_ui.min_size();
|
||||||
|
@ -1017,10 +959,10 @@ impl Ui {
|
||||||
};
|
};
|
||||||
self.child_ui(
|
self.child_ui(
|
||||||
Rect::from_min_size(
|
Rect::from_min_size(
|
||||||
self.region.cursor + vec2(x, 0.0),
|
self.cursor() + vec2(x, 0.0),
|
||||||
vec2(width, self.available_size_before_wrap().y),
|
vec2(width, self.available_size_before_wrap().y),
|
||||||
),
|
),
|
||||||
self.layout,
|
*self.layout(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1116,7 +1058,7 @@ impl Ui {
|
||||||
self.style().spacing.interact_size.y, // Assume there will be something interactive on the horizontal layout
|
self.style().spacing.interact_size.y, // Assume there will be something interactive on the horizontal layout
|
||||||
);
|
);
|
||||||
|
|
||||||
let layout = if self.layout.prefer_right_to_left() {
|
let layout = if self.placer.prefer_right_to_left() {
|
||||||
Layout::right_to_left()
|
Layout::right_to_left()
|
||||||
} else {
|
} else {
|
||||||
Layout::left_to_right()
|
Layout::left_to_right()
|
||||||
|
@ -1161,12 +1103,23 @@ impl Ui {
|
||||||
let ret = add_contents(&mut child_ui);
|
let ret = add_contents(&mut child_ui);
|
||||||
let rect = child_ui.min_rect();
|
let rect = child_ui.min_rect();
|
||||||
let item_spacing = self.style().spacing.item_spacing;
|
let item_spacing = self.style().spacing.item_spacing;
|
||||||
self.layout
|
self.placer
|
||||||
.advance_after_outer_rect(&mut self.region, rect, rect, item_spacing);
|
.advance_after_outer_rect(rect, rect, item_spacing);
|
||||||
self.region.expand_to_include_rect(rect);
|
self.expand_to_include_rect(rect);
|
||||||
(ret, self.interact(rect, child_ui.id, Sense::hover()))
|
(ret, self.interact(rect, child_ui.id, Sense::hover()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_grid(&mut self, grid: grid::GridLayout) {
|
||||||
|
self.placer.set_grid(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move to the next row in a grid layout or wrapping layout.
|
||||||
|
/// Otherwise does nothing.
|
||||||
|
pub fn end_row(&mut self) {
|
||||||
|
self.placer
|
||||||
|
.end_row(self.style().spacing.item_spacing, &self.painter().clone());
|
||||||
|
}
|
||||||
|
|
||||||
/// Temporarily split split an Ui into several columns.
|
/// Temporarily split split an Ui into several columns.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -1184,7 +1137,7 @@ impl Ui {
|
||||||
let spacing = self.style().spacing.item_spacing.x;
|
let spacing = self.style().spacing.item_spacing.x;
|
||||||
let total_spacing = spacing * (num_columns as f32 - 1.0);
|
let total_spacing = spacing * (num_columns as f32 - 1.0);
|
||||||
let column_width = (self.available_width() - total_spacing) / (num_columns as f32);
|
let column_width = (self.available_width() - total_spacing) / (num_columns as f32);
|
||||||
let top_left = self.region.cursor;
|
let top_left = self.cursor();
|
||||||
|
|
||||||
let mut columns: Vec<Self> = (0..num_columns)
|
let mut columns: Vec<Self> = (0..num_columns)
|
||||||
.map(|col_idx| {
|
.map(|col_idx| {
|
||||||
|
@ -1224,6 +1177,6 @@ impl Ui {
|
||||||
impl Ui {
|
impl Ui {
|
||||||
/// Shows where the next widget is going to be placed
|
/// Shows where the next widget is going to be placed
|
||||||
pub fn debug_paint_cursor(&self) {
|
pub fn debug_paint_cursor(&self) {
|
||||||
self.layout.debug_paint_cursor(&self.region, &self.painter);
|
self.placer.debug_paint_cursor(&self.painter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ struct Demos {
|
||||||
impl Default for Demos {
|
impl Default for Demos {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let demos: Vec<Box<dyn super::Demo>> = vec![
|
let demos: Vec<Box<dyn super::Demo>> = vec![
|
||||||
|
Box::new(super::WidgetGallery::default()),
|
||||||
Box::new(super::FontBook::default()),
|
Box::new(super::FontBook::default()),
|
||||||
Box::new(super::Painting::default()),
|
Box::new(super::Painting::default()),
|
||||||
Box::new(super::DancingStrings::default()),
|
Box::new(super::DancingStrings::default()),
|
||||||
|
|
|
@ -17,13 +17,14 @@ mod scrolls;
|
||||||
mod sliders;
|
mod sliders;
|
||||||
mod tests;
|
mod tests;
|
||||||
pub mod toggle_switch;
|
pub mod toggle_switch;
|
||||||
|
mod widget_gallery;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
mod window_options;
|
mod window_options;
|
||||||
|
|
||||||
pub use {
|
pub use {
|
||||||
app::*, dancing_strings::DancingStrings, demo_window::DemoWindow, demo_windows::*,
|
app::*, dancing_strings::DancingStrings, demo_window::DemoWindow, demo_windows::*,
|
||||||
drag_and_drop::*, font_book::FontBook, painting::Painting, scrolls::Scrolls, sliders::Sliders,
|
drag_and_drop::*, font_book::FontBook, painting::Painting, scrolls::Scrolls, sliders::Sliders,
|
||||||
tests::Tests, widgets::Widgets, window_options::WindowOptions,
|
tests::Tests, widget_gallery::*, widgets::Widgets, window_options::WindowOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
@ -75,7 +75,7 @@ impl super::Demo for Painting {
|
||||||
|
|
||||||
impl super::View for Painting {
|
impl super::View for Painting {
|
||||||
fn ui(&mut self, ui: &mut Ui) {
|
fn ui(&mut self, ui: &mut Ui) {
|
||||||
ui.add(crate::__egui_github_link_file!("(source code)"));
|
ui.add(crate::__egui_github_link_file!());
|
||||||
self.ui_control(ui);
|
self.ui_control(ui);
|
||||||
ui.label("Paint with your mouse/touch!");
|
ui.label("Paint with your mouse/touch!");
|
||||||
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
Frame::dark_canvas(ui.style()).show(ui, |ui| {
|
||||||
|
|
117
egui_demo_lib/src/apps/demo/widget_gallery.rs
Normal file
117
egui_demo_lib/src/apps/demo/widget_gallery.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
enum Enum {
|
||||||
|
First,
|
||||||
|
Second,
|
||||||
|
Third,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub struct WidgetGallery {
|
||||||
|
boolean: bool,
|
||||||
|
radio: Enum,
|
||||||
|
scalar: f32,
|
||||||
|
string: String,
|
||||||
|
color: egui::Color32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WidgetGallery {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
boolean: false,
|
||||||
|
radio: Enum::First,
|
||||||
|
scalar: 42.0,
|
||||||
|
string: "Hello World!".to_owned(),
|
||||||
|
color: egui::Color32::LIGHT_BLUE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::Demo for WidgetGallery {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"🗄 Widget Gallery"
|
||||||
|
}
|
||||||
|
|
||||||
|
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 WidgetGallery {
|
||||||
|
fn ui(&mut self, ui: &mut egui::Ui) {
|
||||||
|
let Self {
|
||||||
|
boolean,
|
||||||
|
radio,
|
||||||
|
scalar,
|
||||||
|
string,
|
||||||
|
color,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
egui::Grid::new("my_grid").striped(true).show(ui, |ui| {
|
||||||
|
ui.label("Label:");
|
||||||
|
ui.label("Welcome to the widget gallery!");
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Text Input:");
|
||||||
|
ui.text_edit_singleline(string);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Checkbox:");
|
||||||
|
ui.checkbox(boolean, "Checkbox");
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Radio buttons:");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.radio_value(radio, Enum::First, "First");
|
||||||
|
ui.radio_value(radio, Enum::Second, "Second");
|
||||||
|
ui.radio_value(radio, Enum::Third, "Third");
|
||||||
|
});
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("ComboBox:");
|
||||||
|
egui::combo_box_with_label(ui, "Take your pick", format!("{:?}", radio), |ui| {
|
||||||
|
ui.selectable_value(radio, Enum::First, "First");
|
||||||
|
ui.selectable_value(radio, Enum::Second, "Second");
|
||||||
|
ui.selectable_value(radio, Enum::Third, "Third");
|
||||||
|
});
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Slider:");
|
||||||
|
ui.add(egui::Slider::f32(scalar, 0.0..=100.0).text("value"));
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("DragValue:");
|
||||||
|
ui.add(egui::DragValue::f32(scalar).speed(1.0));
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Color picker:");
|
||||||
|
ui.color_edit_button_srgba(color);
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Image:");
|
||||||
|
ui.image(egui::TextureId::Egui, [24.0, 16.0])
|
||||||
|
.on_hover_text("The font texture");
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("Button:");
|
||||||
|
if ui.button("Toggle boolean").clicked {
|
||||||
|
*boolean = !*boolean;
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
|
||||||
|
ui.label("ImageButton:");
|
||||||
|
if ui
|
||||||
|
.add(egui::ImageButton::new(egui::TextureId::Egui, [24.0, 16.0]))
|
||||||
|
.clicked
|
||||||
|
{
|
||||||
|
*boolean = !*boolean;
|
||||||
|
}
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add(crate::__egui_github_link_file!());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue