commit
884558ac48
22 changed files with 1031 additions and 555 deletions
|
@ -8,12 +8,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Added ⭐
|
||||
|
||||
* `SelectableLabel` (`ui.selectable_label` and `ui.selectable_value`): a text-button that can be selected
|
||||
* Wrapping layouts:
|
||||
* `ui.horizontal_wrapped(|ui| ...)`: Add widgets on a row but wrap at `max_size`.
|
||||
* `ui.horizontal_wrapped_for_text`: Like `horizontal_wrapped`, but with spacing made for embedding text.
|
||||
* `egui::Layout` now supports justified layouts where contents is _also_ centered, right-aligned, etc.
|
||||
* `ui.allocate_ui(size, |ui| ...)`: Easily created a sized child-`Ui`.
|
||||
* `SelectableLabel` (`ui.selectable_label` and `ui.selectable_value`): A text-button that can be selected.
|
||||
* `ui.small_button`: A smaller button that looks good embedded in text.
|
||||
* Add `Resize::id_source` and `ScrollArea::id_source` to let the user avoid Id clashes.
|
||||
|
||||
### Changed 🔧
|
||||
|
||||
* Changed default font to [Ubuntu-Light](https://fonts.google.com/specimen/Ubuntu).
|
||||
* Refactored `egui::Layout` substantially, changing its interface.
|
||||
|
||||
### Removed 🔥
|
||||
|
||||
|
|
|
@ -16,6 +16,21 @@ pub enum Align {
|
|||
Max,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
pub fn left() -> Self {
|
||||
Self::Min
|
||||
}
|
||||
pub fn right() -> Self {
|
||||
Self::Max
|
||||
}
|
||||
pub fn top() -> Self {
|
||||
Self::Min
|
||||
}
|
||||
pub fn bottom() -> Self {
|
||||
Self::Max
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Align {
|
||||
fn default() -> Align {
|
||||
Align::Min
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::hash::Hash;
|
||||
|
||||
use crate::{
|
||||
layout::Direction,
|
||||
paint::{PaintCmd, TextStyle},
|
||||
widgets::Label,
|
||||
*,
|
||||
|
@ -168,7 +167,7 @@ struct Prepared {
|
|||
impl CollapsingHeader {
|
||||
fn begin(self, ui: &mut Ui) -> Prepared {
|
||||
assert!(
|
||||
ui.layout().dir() == Direction::Vertical,
|
||||
ui.layout().main_dir().is_vertical(),
|
||||
"Horizontal collapsing is unimplemented"
|
||||
);
|
||||
let Self {
|
||||
|
@ -181,7 +180,7 @@ impl CollapsingHeader {
|
|||
|
||||
let id = ui.make_persistent_id(id_source);
|
||||
|
||||
let available = ui.available_finite();
|
||||
let available = ui.available_rect_before_wrap_finite();
|
||||
let text_pos = available.min + vec2(ui.style().spacing.indent, 0.0);
|
||||
let galley = label.layout_width(ui, available.right() - text_pos.x);
|
||||
let text_max_x = text_pos.x + galley.size.x;
|
||||
|
|
|
@ -60,10 +60,13 @@ pub fn combo_box(
|
|||
let frame = Frame::popup(ui.style());
|
||||
let frame_margin = frame.margin;
|
||||
frame.show(ui, |ui| {
|
||||
ui.with_layout(Layout::justified(Direction::Vertical), |ui| {
|
||||
ui.with_layout(
|
||||
Layout::top_down(Align::left()).with_cross_justify(true),
|
||||
|ui| {
|
||||
ui.set_min_width(button_response.rect.width() - 2.0 * frame_margin.x);
|
||||
menu_contents(ui);
|
||||
});
|
||||
},
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -84,7 +87,7 @@ fn button_frame(
|
|||
add_contents: impl FnOnce(&mut Ui),
|
||||
) -> Response {
|
||||
let margin = ui.style().spacing.button_padding;
|
||||
let outer_rect_bounds = ui.available();
|
||||
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||
let inner_rect = outer_rect_bounds.shrink2(margin);
|
||||
let where_to_put_background = ui.painter().add(PaintCmd::Noop);
|
||||
let mut content_ui = ui.child_ui(inner_rect, *ui.layout());
|
||||
|
|
|
@ -99,7 +99,7 @@ pub struct Prepared {
|
|||
impl Frame {
|
||||
pub fn begin(self, ui: &mut Ui) -> Prepared {
|
||||
let where_to_put_background = ui.painter().add(PaintCmd::Noop);
|
||||
let outer_rect_bounds = ui.available();
|
||||
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||
let inner_rect = outer_rect_bounds.shrink2(self.margin);
|
||||
let content_ui = ui.child_ui(inner_rect, *ui.layout());
|
||||
|
||||
|
|
|
@ -153,7 +153,7 @@ struct Prepared {
|
|||
|
||||
impl Resize {
|
||||
fn begin(&mut self, ui: &mut Ui) -> Prepared {
|
||||
let position = ui.available().min;
|
||||
let position = ui.available_rect_before_wrap().min;
|
||||
let id = self.id.unwrap_or_else(|| {
|
||||
let id_source = self.id_source.unwrap_or_else(|| Id::new("resize"));
|
||||
ui.make_persistent_id(id_source)
|
||||
|
|
|
@ -103,12 +103,12 @@ impl ScrollArea {
|
|||
};
|
||||
|
||||
let outer_size = vec2(
|
||||
ui.available().width(),
|
||||
ui.available().height().at_most(max_height),
|
||||
ui.available_width(),
|
||||
ui.available_size_before_wrap().y.at_most(max_height),
|
||||
);
|
||||
|
||||
let inner_size = outer_size - vec2(current_scroll_bar_width, 0.0);
|
||||
let inner_rect = Rect::from_min_size(ui.available().min, inner_size);
|
||||
let inner_rect = Rect::from_min_size(ui.available_rect_before_wrap().min, inner_size);
|
||||
|
||||
let mut content_ui = ui.child_ui(
|
||||
Rect::from_min_size(
|
||||
|
|
|
@ -106,7 +106,7 @@ impl FrameHistory {
|
|||
|
||||
// TODO: we should not use `slider_width` as default graph width.
|
||||
let height = ui.style().spacing.slider_width;
|
||||
let rect = ui.allocate_space(vec2(ui.available_finite().width(), height));
|
||||
let rect = ui.allocate_space(vec2(ui.available_size_before_wrap_finite().x, height));
|
||||
let style = ui.style().noninteractive();
|
||||
|
||||
let mut cmds = vec![PaintCmd::Rect {
|
||||
|
|
|
@ -32,7 +32,7 @@ impl View for DancingStrings {
|
|||
ui.ctx().request_repaint();
|
||||
let time = ui.input().time;
|
||||
|
||||
let desired_size = ui.available().width() * vec2(1.0, 0.35);
|
||||
let desired_size = ui.available_width() * vec2(1.0, 0.35);
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
|
||||
let mut cmds = vec![];
|
||||
|
|
|
@ -265,7 +265,7 @@ impl Painting {
|
|||
}
|
||||
|
||||
fn content(&mut self, ui: &mut Ui) {
|
||||
let rect = ui.allocate_space(ui.available_finite().size());
|
||||
let rect = ui.allocate_space(ui.available_size_before_wrap_finite());
|
||||
let response = ui.interact(rect, ui.id(), Sense::drag());
|
||||
let rect = response.rect;
|
||||
let clip_rect = ui.clip_rect().intersect(rect); // Make sure we don't paint out of bounds
|
||||
|
@ -304,81 +304,134 @@ use crate::layout::*;
|
|||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
struct LayoutDemo {
|
||||
dir: Direction,
|
||||
align: Option<Align>, // None == justified
|
||||
reversed: bool,
|
||||
// Identical to contents of `egui::Layout`
|
||||
main_dir: Direction,
|
||||
main_wrap: bool,
|
||||
cross_align: Align,
|
||||
cross_justify: bool,
|
||||
|
||||
// Extra for testing wrapping:
|
||||
wrap_column_width: f32,
|
||||
wrap_row_height: f32,
|
||||
}
|
||||
|
||||
impl Default for LayoutDemo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dir: Direction::Vertical,
|
||||
align: Some(Align::Center),
|
||||
reversed: false,
|
||||
main_dir: Direction::TopDown,
|
||||
main_wrap: false,
|
||||
cross_align: Align::Min,
|
||||
cross_justify: false,
|
||||
wrap_column_width: 150.0,
|
||||
wrap_row_height: 20.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutDemo {
|
||||
fn layout(&self) -> Layout {
|
||||
let layout = Layout::from_dir_align(self.dir, self.align);
|
||||
if self.reversed {
|
||||
layout.reverse()
|
||||
} else {
|
||||
layout
|
||||
}
|
||||
Layout::from_main_dir_and_cross_align(self.main_dir, self.cross_align)
|
||||
.with_main_wrap(self.main_wrap)
|
||||
.with_cross_justify(self.cross_justify)
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
self.content_ui(ui);
|
||||
Resize::default()
|
||||
.default_size([200.0, 100.0])
|
||||
.default_size([300.0, 200.0])
|
||||
.show(ui, |ui| {
|
||||
ui.with_layout(self.layout(), |ui| self.content_ui(ui))
|
||||
if self.main_wrap {
|
||||
if self.main_dir.is_horizontal() {
|
||||
ui.allocate_ui(
|
||||
vec2(
|
||||
ui.available_size_before_wrap_finite().x,
|
||||
self.wrap_row_height,
|
||||
),
|
||||
|ui| ui.with_layout(self.layout(), |ui| self.demo_ui(ui)),
|
||||
);
|
||||
} else {
|
||||
ui.allocate_ui(
|
||||
vec2(
|
||||
self.wrap_column_width,
|
||||
ui.available_size_before_wrap_finite().y,
|
||||
),
|
||||
|ui| ui.with_layout(self.layout(), |ui| self.demo_ui(ui)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ui.with_layout(self.layout(), |ui| self.demo_ui(ui));
|
||||
}
|
||||
});
|
||||
ui.label("Resize to see effect");
|
||||
}
|
||||
|
||||
pub fn content_ui(&mut self, ui: &mut Ui) {
|
||||
// ui.label(format!("Available space: {:?}", ui.available().size()));
|
||||
if ui.button("Reset").clicked {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Top-down").clicked {
|
||||
*self = Default::default();
|
||||
}
|
||||
ui.separator();
|
||||
ui.label("Direction:");
|
||||
if ui.button("Top-down, centered and justified").clicked {
|
||||
*self = Default::default();
|
||||
self.cross_align = Align::Center;
|
||||
self.cross_justify = true;
|
||||
}
|
||||
if ui.button("Horizontal wrapped").clicked {
|
||||
*self = Default::default();
|
||||
self.main_dir = Direction::LeftToRight;
|
||||
self.cross_align = Align::Center;
|
||||
self.main_wrap = true;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: enum iter
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Main Direction:");
|
||||
for &dir in &[
|
||||
Direction::LeftToRight,
|
||||
Direction::RightToLeft,
|
||||
Direction::TopDown,
|
||||
Direction::BottomUp,
|
||||
] {
|
||||
ui.radio_value(&mut self.main_dir, dir, format!("{:?}", dir));
|
||||
}
|
||||
});
|
||||
|
||||
for &dir in &[Direction::Horizontal, Direction::Vertical] {
|
||||
if ui
|
||||
.add(RadioButton::new(self.dir == dir, format!("{:?}", dir)))
|
||||
.clicked
|
||||
{
|
||||
self.dir = dir;
|
||||
ui.horizontal(|ui| {
|
||||
ui.checkbox(&mut self.main_wrap, "Main wrap")
|
||||
.on_hover_text("Wrap when next widget doesn't fit the current row/column");
|
||||
|
||||
if self.main_wrap {
|
||||
if self.main_dir.is_horizontal() {
|
||||
ui.add(Slider::f32(&mut self.wrap_row_height, 0.0..=200.0).text("Row height"));
|
||||
} else {
|
||||
ui.add(
|
||||
Slider::f32(&mut self.wrap_column_width, 0.0..=200.0).text("Column width"),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.checkbox(&mut self.reversed, "Reversed");
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Align:");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Cross Align:");
|
||||
for &align in &[Align::Min, Align::Center, Align::Max] {
|
||||
if ui
|
||||
.add(RadioButton::new(
|
||||
self.align == Some(align),
|
||||
format!("{:?}", align),
|
||||
))
|
||||
.clicked
|
||||
{
|
||||
self.align = Some(align);
|
||||
ui.radio_value(&mut self.cross_align, align, format!("{:?}", align));
|
||||
}
|
||||
});
|
||||
|
||||
ui.checkbox(&mut self.cross_justify, "Cross Justified")
|
||||
.on_hover_text("Try to fill full width/height (e.g. buttons)");
|
||||
}
|
||||
if ui
|
||||
.add(RadioButton::new(self.align == None, "Justified"))
|
||||
.on_hover_text("Try to fill full width/height (e.g. buttons)")
|
||||
.clicked
|
||||
{
|
||||
self.align = None;
|
||||
|
||||
pub fn demo_ui(&mut self, ui: &mut Ui) {
|
||||
ui.monospace("Example widgets:");
|
||||
for _ in 0..3 {
|
||||
ui.label("label");
|
||||
}
|
||||
for _ in 0..3 {
|
||||
let mut dummy = false;
|
||||
ui.checkbox(&mut dummy, "checkbox");
|
||||
}
|
||||
for _ in 0..3 {
|
||||
let _ = ui.button("button");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -345,7 +345,7 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows, seconds_since_midnight:
|
|||
(time % 1.0 * 100.0).floor()
|
||||
);
|
||||
|
||||
ui.with_layout(Layout::horizontal(Align::Center).reverse(), |ui| {
|
||||
ui.with_layout(Layout::right_to_left(), |ui| {
|
||||
if ui
|
||||
.add(Button::new(time).text_style(TextStyle::Monospace))
|
||||
.clicked
|
||||
|
|
|
@ -44,7 +44,7 @@ pub fn drop_target<R>(
|
|||
|
||||
let margin = Vec2::splat(4.0);
|
||||
|
||||
let outer_rect_bounds = ui.available();
|
||||
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||
let inner_rect = outer_rect_bounds.shrink2(margin);
|
||||
let where_to_put_background = ui.painter().add(PaintCmd::Noop);
|
||||
let mut content_ui = ui.child_ui(inner_rect, *ui.layout());
|
||||
|
|
|
@ -52,8 +52,12 @@ impl FractalClock {
|
|||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
let painter = Painter::new(ui.ctx().clone(), ui.layer_id(), ui.available_finite());
|
||||
self.fractal_ui(&painter);
|
||||
let painter = Painter::new(
|
||||
ui.ctx().clone(),
|
||||
ui.layer_id(),
|
||||
ui.available_rect_before_wrap_finite(),
|
||||
);
|
||||
self.paint(&painter);
|
||||
|
||||
Frame::popup(ui.style())
|
||||
.fill(Rgba::luminance_alpha(0.02, 0.5).into())
|
||||
|
@ -97,7 +101,7 @@ impl FractalClock {
|
|||
);
|
||||
}
|
||||
|
||||
fn fractal_ui(&mut self, painter: &Painter) {
|
||||
fn paint(&mut self, painter: &Painter) {
|
||||
let rect = painter.clip_rect();
|
||||
|
||||
struct Hand {
|
||||
|
|
|
@ -46,28 +46,34 @@ impl Default for Widgets {
|
|||
|
||||
impl Widgets {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.add(crate::__egui_github_link_file_line!());
|
||||
ui.add(__egui_github_link_file_line!());
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.style_mut().spacing.item_spacing.x = 0.0;
|
||||
ui.add(Label::new("Text can have ").text_color(srgba(110, 255, 110, 255)));
|
||||
ui.add(Label::new("color ").text_color(srgba(128, 140, 255, 255)));
|
||||
ui.horizontal_wrapped_for_text(TextStyle::Body, |ui| {
|
||||
ui.label("Long text will wrap, just as you would expect.");
|
||||
ui.add(Label::new("Text can have").text_color(srgba(110, 255, 110, 255)));
|
||||
ui.add(Label::new("color").text_color(srgba(128, 140, 255, 255)));
|
||||
ui.add(Label::new("and tooltips.")).on_hover_text(
|
||||
"This is a multiline tooltip that demonstrates that you can easily add tooltips to any element.\nThis is the second line.\nThis is the third.",
|
||||
);
|
||||
|
||||
ui.label("You can mix in other widgets into text, like this");
|
||||
let _ = ui.small_button("button");
|
||||
ui.label(".");
|
||||
|
||||
ui.label("There is also (limited) non-ASCII support: Ευρηκα! τ = 2×π")
|
||||
.on_hover_text("The current font supports only a few non-latin characters and Egui does not currently support right-to-left text.");
|
||||
});
|
||||
ui.label("Tooltips can be more than just simple text.")
|
||||
.on_hover_ui(|ui| {
|
||||
|
||||
let tooltip_ui = |ui: &mut Ui| {
|
||||
ui.heading("The name of the tooltip");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("This tooltip was created with");
|
||||
ui.monospace(".on_hover_ui(...)");
|
||||
});
|
||||
let _ = ui.button("A button you can never press");
|
||||
});
|
||||
|
||||
ui.label("Ευρηκα! τ = 2×π")
|
||||
.on_hover_text("The current font supports only a few non-latin characters and Egui does not currently support right-to-left text.");
|
||||
};
|
||||
ui.label("Tooltips can be more than just simple text.")
|
||||
.on_hover_ui(tooltip_ui);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.radio_value(&mut self.radio, Enum::First, "First");
|
||||
|
|
|
@ -16,8 +16,8 @@ impl Texture {
|
|||
return;
|
||||
}
|
||||
let mut size = vec2(self.width as f32, self.height as f32);
|
||||
if size.x > ui.available().width() {
|
||||
size *= ui.available().width() / size.x;
|
||||
if size.x > ui.available_width() {
|
||||
size *= ui.available_width() / size.x;
|
||||
}
|
||||
let rect = ui.allocate_space(size);
|
||||
let mut triangles = Triangles::default();
|
||||
|
|
|
@ -2,18 +2,92 @@ use crate::{math::*, Align};
|
|||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// `Layout` direction (horizontal or vertical).
|
||||
/// This describes the bounds and existing contents of an `Ui`.
|
||||
/// It is what is used and updated by `Layout` when adding new widgets.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Region {
|
||||
/// This is the minimal size of the `Ui`.
|
||||
/// When adding new widgets, this will generally expand.
|
||||
///
|
||||
/// Always finite.
|
||||
///
|
||||
/// The bounding box of all child widgets, but not necessarily a tight bounding box
|
||||
/// since `Ui` can start with a non-zero min_rect size.
|
||||
pub min_rect: Rect,
|
||||
|
||||
/// The maximum size of this `Ui`. This is a *soft max*
|
||||
/// meaning new widgets will *try* not to expand beyond it,
|
||||
/// but if they have to, they will.
|
||||
///
|
||||
/// Text will wrap at `max_rect.right()`.
|
||||
/// Some widgets (like separator lines) will try to fill the full `max_rect` width of the ui.
|
||||
///
|
||||
/// `max_rect` will always be at least the size of `min_rect`.
|
||||
///
|
||||
/// If the `max_rect` size is zero, it is a signal that child widgets should be as small as possible.
|
||||
/// If the `max_rect` size is infinite, it is a signal that child widgets should take up as much room as they want.
|
||||
pub max_rect: Rect,
|
||||
|
||||
/// Where the next widget will be put.
|
||||
/// If something has already been added, this will point ot `style.spacing.item_spacing` beyond the latest child.
|
||||
/// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect.
|
||||
pub(crate) cursor: Pos2,
|
||||
}
|
||||
|
||||
impl Region {
|
||||
/// This is like `max_rect`, but will never be infinite.
|
||||
/// If the desired rect is infinite ("be as big as you want")
|
||||
/// this will be bounded by `min_rect` instead.
|
||||
pub fn max_rect_finite(&self) -> Rect {
|
||||
let mut result = self.max_rect;
|
||||
if !result.min.x.is_finite() {
|
||||
result.min.x = self.min_rect.min.x;
|
||||
}
|
||||
if !result.min.y.is_finite() {
|
||||
result.min.y = self.min_rect.min.y;
|
||||
}
|
||||
if !result.max.x.is_finite() {
|
||||
result.max.x = self.min_rect.max.x;
|
||||
}
|
||||
if !result.max.y.is_finite() {
|
||||
result.max.y = self.min_rect.max.y;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
self.min_rect = self.min_rect.union(rect);
|
||||
self.max_rect = self.max_rect.union(rect);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Main layout direction
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
pub enum Direction {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
LeftToRight,
|
||||
RightToLeft,
|
||||
TopDown,
|
||||
BottomUp,
|
||||
}
|
||||
|
||||
impl Default for Direction {
|
||||
fn default() -> Direction {
|
||||
Direction::Vertical
|
||||
impl Direction {
|
||||
pub fn is_horizontal(self) -> bool {
|
||||
match self {
|
||||
Direction::LeftToRight | Direction::RightToLeft => true,
|
||||
Direction::TopDown | Direction::BottomUp => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_vertical(self) -> bool {
|
||||
match self {
|
||||
Direction::LeftToRight | Direction::RightToLeft => false,
|
||||
Direction::TopDown | Direction::BottomUp => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,255 +97,392 @@ impl Default for Direction {
|
|||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
// #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Layout {
|
||||
/// Lay out things horizontally or vertically? Main axis.
|
||||
dir: Direction,
|
||||
/// Main axis direction
|
||||
main_dir: Direction,
|
||||
|
||||
/// If true, wrap around when reading the end of the main direction.
|
||||
/// For instance, for `main_dir == Direction::LeftToRight` this will
|
||||
/// wrap to a new row when we reach the right side of the `max_rect`.
|
||||
main_wrap: 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?
|
||||
/// `None` means justified, which means full width (vertical layout) or height (horizontal layouts).
|
||||
align: Option<Align>,
|
||||
cross_align: Align,
|
||||
|
||||
/// Lay out things in reversed order, i.e. from the right or bottom-up.
|
||||
reversed: bool,
|
||||
/// Justify the cross axis?
|
||||
/// For vertical layouts justify mean all widgets get maximum width.
|
||||
/// For horizontal layouts justify mean all widgets get maximum height.
|
||||
cross_justify: bool,
|
||||
}
|
||||
|
||||
impl Default for Layout {
|
||||
fn default() -> Self {
|
||||
// TODO: Get from `Style` instead.
|
||||
// This is a very euro-centric default.
|
||||
Self {
|
||||
dir: Direction::Vertical,
|
||||
align: Some(Align::Min),
|
||||
reversed: false,
|
||||
main_dir: Direction::TopDown,
|
||||
main_wrap: false,
|
||||
cross_align: Align::left(),
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
/// None align means justified, e.g. fill full width/height.
|
||||
pub fn from_dir_align(dir: Direction, align: Option<Align>) -> Self {
|
||||
pub(crate) fn from_main_dir_and_cross_align(main_dir: Direction, cross_align: Align) -> Self {
|
||||
Self {
|
||||
dir,
|
||||
align,
|
||||
reversed: false,
|
||||
main_dir,
|
||||
main_wrap: false,
|
||||
cross_align,
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vertical(align: Align) -> Self {
|
||||
pub fn left_to_right() -> Self {
|
||||
Self {
|
||||
dir: Direction::Vertical,
|
||||
align: Some(align),
|
||||
reversed: false,
|
||||
main_dir: Direction::LeftToRight,
|
||||
main_wrap: false,
|
||||
cross_align: Align::Center,
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn horizontal(align: Align) -> Self {
|
||||
pub fn right_to_left() -> Self {
|
||||
Self {
|
||||
dir: Direction::Horizontal,
|
||||
align: Some(align),
|
||||
reversed: false,
|
||||
main_dir: Direction::RightToLeft,
|
||||
main_wrap: false,
|
||||
cross_align: Align::Center,
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Full-width layout.
|
||||
/// Nice for menus etc where each button is full width.
|
||||
pub fn justified(dir: Direction) -> Self {
|
||||
pub fn top_down(cross_align: Align) -> Self {
|
||||
Self {
|
||||
dir,
|
||||
align: None,
|
||||
reversed: false,
|
||||
main_dir: Direction::TopDown,
|
||||
main_wrap: false,
|
||||
cross_align,
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn reverse(self) -> Self {
|
||||
pub fn bottom_up(cross_align: Align) -> Self {
|
||||
Self {
|
||||
dir: self.dir,
|
||||
align: self.align,
|
||||
reversed: !self.reversed,
|
||||
main_dir: Direction::BottomUp,
|
||||
main_wrap: false,
|
||||
cross_align,
|
||||
cross_justify: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reversed(self, reversed: bool) -> Self {
|
||||
if reversed {
|
||||
self.reverse()
|
||||
#[deprecated = "Use `top_down`"]
|
||||
pub fn vertical(cross_align: Align) -> Self {
|
||||
Self::top_down(cross_align)
|
||||
}
|
||||
|
||||
#[deprecated = "Use `left_to_right`"]
|
||||
pub fn horizontal(cross_align: Align) -> Self {
|
||||
Self::left_to_right().with_cross_align(cross_align)
|
||||
}
|
||||
|
||||
pub fn with_main_wrap(self, main_wrap: bool) -> Self {
|
||||
Self { main_wrap, ..self }
|
||||
}
|
||||
|
||||
pub fn with_cross_align(self, cross_align: Align) -> Self {
|
||||
Self {
|
||||
cross_align,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_cross_justify(self, cross_justify: bool) -> Self {
|
||||
Self {
|
||||
cross_justify,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
pub fn main_dir(self) -> Direction {
|
||||
self.main_dir
|
||||
}
|
||||
|
||||
pub fn main_wrap(self) -> bool {
|
||||
self.main_wrap
|
||||
}
|
||||
|
||||
pub fn cross_align(self) -> Align {
|
||||
self.cross_align
|
||||
}
|
||||
|
||||
pub fn cross_justify(self) -> bool {
|
||||
self.cross_justify
|
||||
}
|
||||
|
||||
pub fn is_horizontal(self) -> bool {
|
||||
self.main_dir().is_horizontal()
|
||||
}
|
||||
|
||||
pub fn is_vertical(self) -> bool {
|
||||
self.main_dir().is_vertical()
|
||||
}
|
||||
|
||||
pub fn prefer_right_to_left(self) -> bool {
|
||||
self.main_dir == Direction::RightToLeft
|
||||
|| self.main_dir.is_vertical() && self.cross_align == Align::Max
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Direction::LeftToRight | Direction::RightToLeft => self.cross_align,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
fn initial_cursor(self, max_rect: Rect) -> Pos2 {
|
||||
match self.main_dir {
|
||||
Direction::LeftToRight => max_rect.left_top(),
|
||||
Direction::RightToLeft => max_rect.right_top(),
|
||||
Direction::TopDown => max_rect.left_top(),
|
||||
Direction::BottomUp => max_rect.left_bottom(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn region_from_max_rect(&self, max_rect: Rect) -> Region {
|
||||
let cursor = self.initial_cursor(max_rect);
|
||||
let min_rect = Rect::from_min_size(cursor, Vec2::zero());
|
||||
Region {
|
||||
min_rect,
|
||||
max_rect,
|
||||
cursor,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn available_rect_before_wrap(&self, region: &Region) -> Rect {
|
||||
self.available_from_cursor_max_rect(region.cursor, region.max_rect)
|
||||
}
|
||||
|
||||
pub(crate) fn available_size_before_wrap(&self, region: &Region) -> Vec2 {
|
||||
self.available_rect_before_wrap(region).size()
|
||||
}
|
||||
|
||||
pub(crate) fn available_rect_before_wrap_finite(&self, region: &Region) -> Rect {
|
||||
self.available_from_cursor_max_rect(region.cursor, region.max_rect_finite())
|
||||
}
|
||||
|
||||
pub(crate) fn available_size_before_wrap_finite(&self, region: &Region) -> Vec2 {
|
||||
self.available_rect_before_wrap_finite(region).size()
|
||||
}
|
||||
|
||||
/// Amount of space available for a widget.
|
||||
/// Wor wrapping layouts, this is the maximum (after wrap)
|
||||
pub fn available_size(&self, r: &Region) -> Vec2 {
|
||||
if self.main_wrap {
|
||||
if self.main_dir.is_horizontal() {
|
||||
vec2(r.max_rect.width(), r.max_rect.bottom() - r.cursor.y)
|
||||
} else {
|
||||
self
|
||||
vec2(r.max_rect.right() - r.cursor.x, r.max_rect.height())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dir(self) -> Direction {
|
||||
self.dir
|
||||
}
|
||||
|
||||
pub fn align(self) -> Option<Align> {
|
||||
self.align
|
||||
}
|
||||
|
||||
pub fn is_reversed(self) -> bool {
|
||||
self.reversed
|
||||
}
|
||||
|
||||
pub fn initial_cursor(self, max_rect: Rect) -> Pos2 {
|
||||
match self.dir {
|
||||
Direction::Horizontal => {
|
||||
if self.reversed {
|
||||
max_rect.right_top()
|
||||
} else {
|
||||
max_rect.left_top()
|
||||
}
|
||||
}
|
||||
Direction::Vertical => {
|
||||
if self.reversed {
|
||||
max_rect.left_bottom()
|
||||
} else {
|
||||
max_rect.left_top()
|
||||
}
|
||||
}
|
||||
self.available_from_cursor_max_rect(r.cursor, r.max_rect)
|
||||
.size()
|
||||
}
|
||||
}
|
||||
|
||||
/// Given the cursor in the region, how much space is available
|
||||
/// for the next widget?
|
||||
pub fn available(self, cursor: Pos2, max_rect: Rect) -> Rect {
|
||||
fn available_from_cursor_max_rect(self, cursor: Pos2, max_rect: Rect) -> Rect {
|
||||
let mut rect = max_rect;
|
||||
match self.dir {
|
||||
Direction::Horizontal => {
|
||||
|
||||
match self.main_dir {
|
||||
Direction::LeftToRight => {
|
||||
rect.min.x = cursor.x;
|
||||
rect.min.y = cursor.y;
|
||||
if self.reversed {
|
||||
}
|
||||
Direction::RightToLeft => {
|
||||
rect.max.x = cursor.x;
|
||||
} else {
|
||||
rect.min.x = cursor.x;
|
||||
}
|
||||
}
|
||||
Direction::Vertical => {
|
||||
rect.min.x = cursor.x;
|
||||
if self.reversed {
|
||||
rect.max.y = cursor.y;
|
||||
} else {
|
||||
rect.min.y = cursor.y;
|
||||
}
|
||||
Direction::TopDown => {
|
||||
rect.min.x = cursor.x;
|
||||
rect.min.y = cursor.y;
|
||||
}
|
||||
Direction::BottomUp => {
|
||||
rect.min.x = cursor.x;
|
||||
rect.max.y = cursor.y;
|
||||
}
|
||||
}
|
||||
|
||||
rect
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
#[allow(clippy::collapsible_if)]
|
||||
pub fn next_space(self, region: &Region, mut child_size: Vec2, item_spacing: Vec2) -> Rect {
|
||||
let mut cursor = region.cursor;
|
||||
|
||||
if self.main_wrap {
|
||||
let available_size = self.available_size_before_wrap(region);
|
||||
match self.main_dir {
|
||||
Direction::LeftToRight => {
|
||||
if available_size.x < child_size.x && region.max_rect.left() < cursor.x {
|
||||
// New row
|
||||
cursor = pos2(
|
||||
region.max_rect.left(),
|
||||
region.max_rect.bottom() + item_spacing.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
Direction::RightToLeft => {
|
||||
if available_size.x < child_size.x && cursor.x < region.max_rect.right() {
|
||||
// New row
|
||||
cursor = pos2(
|
||||
region.max_rect.right(),
|
||||
region.max_rect.bottom() + item_spacing.y,
|
||||
);
|
||||
}
|
||||
}
|
||||
Direction::TopDown => {
|
||||
if available_size.y < child_size.y && region.max_rect.top() < cursor.y {
|
||||
// New column
|
||||
cursor = pos2(
|
||||
region.max_rect.right() + item_spacing.x,
|
||||
region.max_rect.top(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Direction::BottomUp => {
|
||||
if available_size.y < child_size.y && cursor.y < region.max_rect.bottom() {
|
||||
// New column
|
||||
cursor = pos2(
|
||||
region.max_rect.right() + item_spacing.x,
|
||||
region.max_rect.bottom(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let child_pos = match self.main_dir {
|
||||
Direction::LeftToRight => cursor,
|
||||
Direction::RightToLeft => cursor + vec2(-child_size.x, 0.0),
|
||||
Direction::TopDown => cursor,
|
||||
Direction::BottomUp => cursor + vec2(0.0, -child_size.y),
|
||||
};
|
||||
|
||||
Rect::from_min_size(child_pos, child_size)
|
||||
}
|
||||
|
||||
/// Apply justify or alignment after calling `next_space`.
|
||||
pub fn justify_or_align(self, mut rect: Rect, child_size: Vec2) -> Rect {
|
||||
if self.main_dir.is_horizontal() {
|
||||
debug_assert!((rect.width() - child_size.x).abs() < 0.1);
|
||||
if self.cross_justify {
|
||||
rect // 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
|
||||
}
|
||||
} else {
|
||||
debug_assert!((rect.height() - child_size.y).abs() < 0.1);
|
||||
if self.cross_justify {
|
||||
rect // justified: 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the cursor by this many points.
|
||||
pub fn advance_cursor(self, cursor: &mut Pos2, amount: f32) {
|
||||
match self.dir() {
|
||||
Direction::Horizontal => {
|
||||
if self.is_reversed() {
|
||||
cursor.x -= amount;
|
||||
} else {
|
||||
cursor.x += amount;
|
||||
}
|
||||
}
|
||||
Direction::Vertical => {
|
||||
if self.is_reversed() {
|
||||
cursor.y -= amount;
|
||||
} else {
|
||||
cursor.y += amount;
|
||||
}
|
||||
}
|
||||
pub fn advance_cursor(self, region: &mut Region, amount: f32) {
|
||||
match self.main_dir {
|
||||
Direction::LeftToRight => region.cursor.x += amount,
|
||||
Direction::RightToLeft => region.cursor.x -= amount,
|
||||
Direction::TopDown => region.cursor.y += amount,
|
||||
Direction::BottomUp => region.cursor.y -= amount,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the cursor by this spacing
|
||||
pub fn advance_cursor2(self, cursor: &mut Pos2, amount: Vec2) {
|
||||
match self.dir() {
|
||||
Direction::Horizontal => self.advance_cursor(cursor, amount.x),
|
||||
Direction::Vertical => self.advance_cursor(cursor, amount.y),
|
||||
pub fn advance_cursor2(self, region: &mut Region, amount: Vec2) {
|
||||
if self.main_dir.is_horizontal() {
|
||||
self.advance_cursor(region, amount.x)
|
||||
} else {
|
||||
self.advance_cursor(region, amount.y)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rect_from_cursor_size(self, cursor: Pos2, size: Vec2) -> Rect {
|
||||
let mut rect = Rect::from_min_size(cursor, size);
|
||||
|
||||
match self.dir {
|
||||
Direction::Horizontal => {
|
||||
if self.reversed {
|
||||
rect.min.x = cursor.x - size.x;
|
||||
rect.max.x = rect.min.x - size.x
|
||||
}
|
||||
}
|
||||
Direction::Vertical => {
|
||||
if self.reversed {
|
||||
rect.min.y = cursor.y - size.y;
|
||||
rect.max.y = rect.min.y - size.y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rect
|
||||
}
|
||||
|
||||
/// Reserve this much space and move the cursor.
|
||||
/// Returns where to put the widget.
|
||||
///
|
||||
/// ## How sizes are negotiated
|
||||
/// Each widget should have a *minimum desired size* and a *desired size*.
|
||||
/// When asking for space, ask AT LEAST for you minimum, and don't ask for more than you need.
|
||||
/// If you want to fill the space, ask about `available().size()` and use that.
|
||||
///
|
||||
/// You may get MORE space than you asked for, for instance
|
||||
/// for `Justified` aligned layouts, like in menus.
|
||||
///
|
||||
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
|
||||
pub fn allocate_space(
|
||||
/// 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 fn advance_after_outer_rect(
|
||||
self,
|
||||
cursor: &mut Pos2,
|
||||
available_size: Vec2,
|
||||
minimum_child_size: Vec2,
|
||||
) -> Rect {
|
||||
let available_size = available_size.at_least(minimum_child_size);
|
||||
|
||||
let mut child_size = minimum_child_size;
|
||||
let mut child_move = Vec2::default();
|
||||
let mut cursor_change = Vec2::default();
|
||||
|
||||
match self.dir {
|
||||
Direction::Horizontal => {
|
||||
if let Some(align) = self.align {
|
||||
child_move.y += match align {
|
||||
Align::Min => 0.0,
|
||||
Align::Center => 0.5 * (available_size.y - child_size.y),
|
||||
Align::Max => available_size.y - child_size.y,
|
||||
region: &mut Region,
|
||||
outer_rect: Rect,
|
||||
inner_rect: Rect,
|
||||
item_spacing: Vec2,
|
||||
) {
|
||||
region.cursor = match self.main_dir {
|
||||
Direction::LeftToRight => pos2(inner_rect.right() + item_spacing.x, outer_rect.top()),
|
||||
Direction::RightToLeft => pos2(inner_rect.left() - item_spacing.x, outer_rect.top()),
|
||||
Direction::TopDown => pos2(outer_rect.left(), inner_rect.bottom() + item_spacing.y),
|
||||
Direction::BottomUp => pos2(outer_rect.left(), inner_rect.top() - item_spacing.y),
|
||||
};
|
||||
} else {
|
||||
// justified: fill full height
|
||||
child_size.y = child_size.y.max(available_size.y);
|
||||
}
|
||||
|
||||
cursor_change.x += child_size.x;
|
||||
}
|
||||
Direction::Vertical => {
|
||||
if let Some(align) = self.align {
|
||||
child_move.x += match align {
|
||||
Align::Min => 0.0,
|
||||
Align::Center => 0.5 * (available_size.x - child_size.x),
|
||||
Align::Max => available_size.x - child_size.x,
|
||||
};
|
||||
} else {
|
||||
// justified: fill full width
|
||||
child_size.x = child_size.x.max(available_size.x);
|
||||
};
|
||||
cursor_change.y += child_size.y;
|
||||
}
|
||||
}
|
||||
|
||||
if self.is_reversed() {
|
||||
let child_pos = *cursor + child_move;
|
||||
let child_pos = match self.dir {
|
||||
Direction::Horizontal => child_pos + vec2(-child_size.x, 0.0),
|
||||
Direction::Vertical => child_pos + vec2(0.0, -child_size.y),
|
||||
};
|
||||
*cursor -= cursor_change;
|
||||
Rect::from_min_size(child_pos, child_size)
|
||||
} else {
|
||||
let child_pos = *cursor + child_move;
|
||||
*cursor += cursor_change;
|
||||
Rect::from_min_size(child_pos, child_size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -280,31 +491,32 @@ impl Layout {
|
|||
/// ## Debug stuff
|
||||
impl Layout {
|
||||
/// Shows where the next widget is going to be placed
|
||||
pub fn debug_paint_cursor(&self, cursor: Pos2, painter: &crate::Painter) {
|
||||
pub fn debug_paint_cursor(&self, region: &Region, painter: &crate::Painter) {
|
||||
use crate::paint::*;
|
||||
|
||||
let cursor = region.cursor;
|
||||
|
||||
let color = color::GREEN;
|
||||
let stroke = Stroke::new(2.0, color);
|
||||
|
||||
let align;
|
||||
|
||||
match self.dir {
|
||||
Direction::Horizontal => {
|
||||
if self.reversed {
|
||||
painter.debug_arrow(cursor, vec2(-1.0, 0.0), stroke);
|
||||
align = (Align::Max, Align::Min);
|
||||
} else {
|
||||
match self.main_dir {
|
||||
Direction::LeftToRight => {
|
||||
painter.debug_arrow(cursor, vec2(1.0, 0.0), stroke);
|
||||
align = (Align::Min, Align::Min);
|
||||
}
|
||||
Direction::RightToLeft => {
|
||||
painter.debug_arrow(cursor, vec2(-1.0, 0.0), stroke);
|
||||
align = (Align::Max, Align::Min);
|
||||
}
|
||||
Direction::Vertical => {
|
||||
if self.reversed {
|
||||
painter.debug_arrow(cursor, vec2(0.0, -1.0), stroke);
|
||||
align = (Align::Min, Align::Max);
|
||||
} else {
|
||||
Direction::TopDown => {
|
||||
painter.debug_arrow(cursor, vec2(0.0, 1.0), stroke);
|
||||
align = (Align::Min, Align::Min);
|
||||
}
|
||||
Direction::BottomUp => {
|
||||
painter.debug_arrow(cursor, vec2(0.0, -1.0), stroke);
|
||||
align = (Align::Min, Align::Max);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ pub fn bar<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Respo
|
|||
|
||||
// Take full width and fixed height:
|
||||
let height = ui.style().spacing.interact_size.y;
|
||||
ui.set_min_size(vec2(ui.available().width(), height));
|
||||
ui.set_min_size(vec2(ui.available_width(), height));
|
||||
|
||||
add_contents(ui)
|
||||
})
|
||||
|
@ -111,7 +111,10 @@ fn menu_impl<'c>(
|
|||
style.visuals.widgets.inactive.bg_fill = TRANSPARENT;
|
||||
style.visuals.widgets.inactive.bg_stroke = Stroke::none();
|
||||
ui.set_style(style);
|
||||
ui.with_layout(Layout::justified(Direction::Vertical), add_contents);
|
||||
ui.with_layout(
|
||||
Layout::top_down(Align::left()).with_cross_justify(true),
|
||||
add_contents,
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
|
|
|
@ -119,6 +119,10 @@ impl Font {
|
|||
self.glyph_infos.read().get(&c).and_then(|gi| gi.uv_rect)
|
||||
}
|
||||
|
||||
pub fn glyph_width(&self, c: char) -> f32 {
|
||||
self.glyph_info(c).advance_width
|
||||
}
|
||||
|
||||
/// `\n` will (intentionally) show up as '?' (`REPLACEMENT_CHAR`)
|
||||
fn glyph_info(&self, c: char) -> GlyphInfo {
|
||||
{
|
||||
|
@ -163,7 +167,20 @@ impl Font {
|
|||
galley
|
||||
}
|
||||
|
||||
/// Always returns at least one row.
|
||||
pub fn layout_multiline(&self, text: String, max_width_in_points: f32) -> Galley {
|
||||
self.layout_multiline_with_indentation_and_max_width(text, 0.0, max_width_in_points)
|
||||
}
|
||||
|
||||
/// * `first_row_indentation`: extra space before the very first character (in points).
|
||||
/// * `max_width_in_points`: wrapping width.
|
||||
/// Always returns at least one row.
|
||||
pub fn layout_multiline_with_indentation_and_max_width(
|
||||
&self,
|
||||
text: String,
|
||||
first_row_indentation: f32,
|
||||
max_width_in_points: f32,
|
||||
) -> Galley {
|
||||
let row_height = self.row_height();
|
||||
let mut cursor_y = 0.0;
|
||||
let mut rows = Vec::new();
|
||||
|
@ -178,8 +195,16 @@ impl Font {
|
|||
|
||||
assert!(paragraph_start <= paragraph_end);
|
||||
let paragraph_text = &text[paragraph_start..paragraph_end];
|
||||
let mut paragraph_rows =
|
||||
self.layout_paragraph_max_width(paragraph_text, max_width_in_points);
|
||||
let line_indentation = if rows.is_empty() {
|
||||
first_row_indentation
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let mut paragraph_rows = self.layout_paragraph_max_width(
|
||||
paragraph_text,
|
||||
line_indentation,
|
||||
max_width_in_points,
|
||||
);
|
||||
assert!(!paragraph_rows.is_empty());
|
||||
paragraph_rows.last_mut().unwrap().ends_with_newline = next_newline.is_some();
|
||||
|
||||
|
@ -248,10 +273,16 @@ impl Font {
|
|||
|
||||
/// A paragraph is text with no line break character in it.
|
||||
/// The text will be wrapped by the given `max_width_in_points`.
|
||||
fn layout_paragraph_max_width(&self, text: &str, max_width_in_points: f32) -> Vec<Row> {
|
||||
/// Always returns at least one row.
|
||||
fn layout_paragraph_max_width(
|
||||
&self,
|
||||
text: &str,
|
||||
mut first_row_indentation: f32,
|
||||
max_width_in_points: f32,
|
||||
) -> Vec<Row> {
|
||||
if text == "" {
|
||||
return vec![Row {
|
||||
x_offsets: vec![0.0],
|
||||
x_offsets: vec![first_row_indentation],
|
||||
y_min: 0.0,
|
||||
y_max: self.row_height(),
|
||||
ends_with_newline: false,
|
||||
|
@ -260,12 +291,7 @@ impl Font {
|
|||
|
||||
let full_x_offsets = self.layout_single_row_fragment(text);
|
||||
|
||||
let mut row_start_x = full_x_offsets[0];
|
||||
|
||||
{
|
||||
#![allow(clippy::float_cmp)]
|
||||
assert_eq!(row_start_x, 0.0);
|
||||
}
|
||||
let mut row_start_x = 0.0; // NOTE: BEFORE the `first_row_indentation`.
|
||||
|
||||
let mut cursor_y = 0.0;
|
||||
let mut row_start_idx = 0;
|
||||
|
@ -277,40 +303,40 @@ impl Font {
|
|||
|
||||
for (i, (x, chr)) in full_x_offsets.iter().skip(1).zip(text.chars()).enumerate() {
|
||||
debug_assert!(chr != '\n');
|
||||
let potential_row_width = x - row_start_x;
|
||||
let potential_row_width = first_row_indentation + x - row_start_x;
|
||||
|
||||
if potential_row_width > max_width_in_points {
|
||||
if let Some(last_space_idx) = last_space {
|
||||
let include_trailing_space = true;
|
||||
let row = if include_trailing_space {
|
||||
Row {
|
||||
// We include the trailing space in the row:
|
||||
let row = Row {
|
||||
x_offsets: full_x_offsets[row_start_idx..=last_space_idx + 1]
|
||||
.iter()
|
||||
.map(|x| x - row_start_x)
|
||||
.map(|x| first_row_indentation + x - row_start_x)
|
||||
.collect(),
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
ends_with_newline: false,
|
||||
}
|
||||
} else {
|
||||
Row {
|
||||
x_offsets: full_x_offsets[row_start_idx..=last_space_idx]
|
||||
.iter()
|
||||
.map(|x| x - row_start_x)
|
||||
.collect(),
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
ends_with_newline: false,
|
||||
}
|
||||
};
|
||||
row.sanity_check();
|
||||
out_rows.push(row);
|
||||
|
||||
row_start_idx = last_space_idx + 1;
|
||||
row_start_x = full_x_offsets[row_start_idx];
|
||||
row_start_x = first_row_indentation + full_x_offsets[row_start_idx];
|
||||
last_space = None;
|
||||
cursor_y += self.row_height();
|
||||
cursor_y = self.round_to_pixel(cursor_y);
|
||||
cursor_y = self.round_to_pixel(cursor_y + self.row_height());
|
||||
} else if out_rows.is_empty() && first_row_indentation > 0.0 {
|
||||
assert_eq!(row_start_idx, 0);
|
||||
// Allow the first row to be completely empty, because we know there will be more space on the next row:
|
||||
let row = Row {
|
||||
x_offsets: vec![first_row_indentation],
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
ends_with_newline: false,
|
||||
};
|
||||
row.sanity_check();
|
||||
out_rows.push(row);
|
||||
cursor_y = self.round_to_pixel(cursor_y + self.row_height());
|
||||
first_row_indentation = 0.0; // Continue all other rows as if there is no indentation
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,7 +350,7 @@ impl Font {
|
|||
let row = Row {
|
||||
x_offsets: full_x_offsets[row_start_idx..]
|
||||
.iter()
|
||||
.map(|x| x - row_start_x)
|
||||
.map(|x| first_row_indentation + x - row_start_x)
|
||||
.collect(),
|
||||
y_min: cursor_y,
|
||||
y_max: cursor_y + self.row_height(),
|
||||
|
|
|
@ -196,6 +196,13 @@ impl Row {
|
|||
self.y_max - self.y_min
|
||||
}
|
||||
|
||||
pub fn rect(&self) -> Rect {
|
||||
Rect::from_min_max(
|
||||
pos2(self.min_x(), self.y_min),
|
||||
pos2(self.max_x(), self.y_max),
|
||||
)
|
||||
}
|
||||
|
||||
/// Closest char at the desired x coordinate.
|
||||
/// Returns something in the range `[0, char_count_excluding_newline()]`.
|
||||
pub fn char_at(&self, desired_x: f32) -> usize {
|
||||
|
|
408
egui/src/ui.rs
408
egui/src/ui.rs
|
@ -22,41 +22,19 @@ pub struct Ui {
|
|||
/// They are therefore only good for Id:s that has no state.
|
||||
next_auto_id: u64,
|
||||
|
||||
/// Specifies paint layer, clip rectangle and a reference to `Context`.
|
||||
painter: Painter,
|
||||
|
||||
/// This is the minimal size of the `Ui`.
|
||||
/// When adding new widgets, this will generally expand.
|
||||
///
|
||||
/// Always finite.
|
||||
///
|
||||
/// The bounding box of all child widgets, but not necessarily a tight bounding box
|
||||
/// since `Ui` can start with a non-zero min_rect size.
|
||||
min_rect: Rect,
|
||||
|
||||
/// The maximum size of this `Ui`. This is a *soft max*
|
||||
/// meaning new widgets will *try* not to expand beyond it,
|
||||
/// but if they have to, they will.
|
||||
///
|
||||
/// Text will wrap at `max_rect.right()`.
|
||||
/// Some widgets (like separator lines) will try to fill the full `max_rect` width of the ui.
|
||||
///
|
||||
/// `max_rect` will always be at least the size of `min_rect`.
|
||||
///
|
||||
/// If the `max_rect` size is zero, it is a signal that child widgets should be as small as possible.
|
||||
/// If the `max_rect` size is infinite, it is a signal that child widgets should take up as much room as they want.
|
||||
max_rect: Rect,
|
||||
|
||||
/// Override default style in this ui
|
||||
/// The `Style` (visuals, spacing, etc) of this ui.
|
||||
/// Commonly many `Ui`:s share the same `Style`.
|
||||
/// The `Ui` implements copy-on-write for this.
|
||||
style: Arc<Style>,
|
||||
|
||||
/// The strategy for where to put the next widget.
|
||||
layout: Layout,
|
||||
|
||||
/// Where the next widget will be put.
|
||||
/// Progresses along self.dir.
|
||||
/// Initially set to rect.min
|
||||
/// If something has already been added, this will point ot style.spacing.item_spacing beyond the latest child.
|
||||
/// The cursor can thus be style.spacing.item_spacing pixels outside of the min_rect.
|
||||
cursor: Pos2, // TODO: move into Layout?
|
||||
/// Sizes/bounds and cursor used by `Layout`.
|
||||
region: Region,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
|
@ -72,36 +50,28 @@ impl Ui {
|
|||
) -> Self {
|
||||
let style = ctx.style();
|
||||
let layout = Layout::default();
|
||||
let cursor = layout.initial_cursor(max_rect);
|
||||
let min_size = Vec2::zero(); // TODO: From Style
|
||||
let min_rect = layout.rect_from_cursor_size(cursor, min_size);
|
||||
let region = layout.region_from_max_rect(max_rect);
|
||||
Ui {
|
||||
id,
|
||||
next_auto_id: id.with("auto").value(),
|
||||
painter: Painter::new(ctx, layer_id, clip_rect),
|
||||
min_rect,
|
||||
max_rect,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
region,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
|
||||
self.next_auto_id = self.next_auto_id.wrapping_add(1);
|
||||
let cursor = layout.initial_cursor(max_rect);
|
||||
let min_size = Vec2::zero(); // TODO: From Style
|
||||
let min_rect = layout.rect_from_cursor_size(cursor, min_size);
|
||||
let region = layout.region_from_max_rect(max_rect);
|
||||
|
||||
Ui {
|
||||
id: self.id.with("child"),
|
||||
next_auto_id: Id::new(self.next_auto_id).with("child").value(),
|
||||
painter: self.painter.clone(),
|
||||
min_rect,
|
||||
max_rect,
|
||||
style: self.style.clone(),
|
||||
layout,
|
||||
cursor,
|
||||
region,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,12 +180,12 @@ impl Ui {
|
|||
///
|
||||
/// This will grow as new widgets are added, but never shrink.
|
||||
pub fn min_rect(&self) -> Rect {
|
||||
self.min_rect
|
||||
self.region.min_rect
|
||||
}
|
||||
|
||||
/// Size of content; same as `min_rect().size()`
|
||||
pub fn min_size(&self) -> Vec2 {
|
||||
self.min_rect.size()
|
||||
self.min_rect().size()
|
||||
}
|
||||
|
||||
/// New widgets will *try* to fit within this rectangle.
|
||||
|
@ -226,32 +196,19 @@ impl Ui {
|
|||
/// 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`.
|
||||
pub fn max_rect(&self) -> Rect {
|
||||
self.max_rect
|
||||
self.region.max_rect
|
||||
}
|
||||
|
||||
/// Used for animation, kind of hacky
|
||||
pub(crate) fn force_set_min_rect(&mut self, min_rect: Rect) {
|
||||
self.min_rect = min_rect;
|
||||
self.region.min_rect = min_rect;
|
||||
}
|
||||
|
||||
/// This is like `max_rect()`, but will never be infinite.
|
||||
/// If the desired rect is infinite ("be as big as you want")
|
||||
/// this will be bounded by `min_rect` instead.
|
||||
pub fn max_rect_finite(&self) -> Rect {
|
||||
let mut result = self.max_rect;
|
||||
if !result.min.x.is_finite() {
|
||||
result.min.x = self.min_rect.min.x;
|
||||
}
|
||||
if !result.min.y.is_finite() {
|
||||
result.min.y = self.min_rect.min.y;
|
||||
}
|
||||
if !result.max.x.is_finite() {
|
||||
result.max.x = self.min_rect.max.x;
|
||||
}
|
||||
if !result.max.y.is_finite() {
|
||||
result.max.y = self.min_rect.max.y;
|
||||
}
|
||||
result
|
||||
self.region.max_rect_finite()
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -266,24 +223,28 @@ impl Ui {
|
|||
/// Set the maximum width of the ui.
|
||||
/// You won't be able to shrink it below the current minimum size.
|
||||
pub fn set_max_width(&mut self, width: f32) {
|
||||
if self.layout.dir() == Direction::Horizontal && self.layout.is_reversed() {
|
||||
debug_assert_eq!(self.min_rect.max.x, self.max_rect.max.x);
|
||||
self.max_rect.min.x = self.max_rect.max.x - width.at_least(self.min_rect.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.max_rect.min.x);
|
||||
self.max_rect.max.x = self.max_rect.min.x + width.at_least(self.min_rect.width());
|
||||
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.
|
||||
/// You won't be able to shrink it below the current minimum size.
|
||||
pub fn set_max_height(&mut self, height: f32) {
|
||||
if self.layout.dir() == Direction::Vertical && self.layout.is_reversed() {
|
||||
debug_assert_eq!(self.min_rect.max.y, self.max_rect.max.y);
|
||||
self.max_rect.min.y = self.max_rect.max.y - height.at_least(self.min_rect.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.max_rect.min.y);
|
||||
self.max_rect.max.y = self.max_rect.min.y + height.at_least(self.min_rect.height());
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,27 +260,31 @@ impl Ui {
|
|||
/// Set the minimum width of the ui.
|
||||
/// This can't shrink the ui, only make it larger.
|
||||
pub fn set_min_width(&mut self, width: f32) {
|
||||
if self.layout.dir() == Direction::Horizontal && self.layout.is_reversed() {
|
||||
debug_assert_eq!(self.min_rect.max.x, self.max_rect.max.x);
|
||||
self.min_rect.min.x = self.min_rect.min.x.min(self.min_rect.max.x - 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.min_rect.min.x, self.max_rect.min.x);
|
||||
self.min_rect.max.x = self.min_rect.max.x.max(self.min_rect.min.x + width);
|
||||
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.max_rect = self.max_rect.union(self.min_rect);
|
||||
self.region.max_rect = self.region.max_rect.union(self.min_rect());
|
||||
}
|
||||
|
||||
/// Set the minimum height of the ui.
|
||||
/// This can't shrink the ui, only make it larger.
|
||||
pub fn set_min_height(&mut self, height: f32) {
|
||||
if self.layout.dir() == Direction::Vertical && self.layout.is_reversed() {
|
||||
debug_assert_eq!(self.min_rect.max.y, self.max_rect.max.y);
|
||||
self.min_rect.min.y = self.min_rect.min.y.min(self.min_rect.max.y - 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.min_rect.min.y, self.max_rect.min.y);
|
||||
self.min_rect.max.y = self.min_rect.max.y.max(self.min_rect.min.y + height);
|
||||
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.max_rect = self.max_rect.union(self.min_rect);
|
||||
self.region.max_rect = self.region.max_rect.union(self.min_rect());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -339,8 +304,7 @@ impl Ui {
|
|||
|
||||
/// 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) {
|
||||
self.min_rect = self.min_rect.union(rect);
|
||||
self.max_rect = self.max_rect.union(rect);
|
||||
self.region.expand_to_include_rect(rect);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
@ -349,18 +313,39 @@ impl Ui {
|
|||
/// The available space at the moment, given the current cursor.
|
||||
/// This how much more space we can take up without overflowing our parent.
|
||||
/// Shrinks as widgets allocate space and the cursor moves.
|
||||
/// A small rectangle should be interpreted as "as little as possible".
|
||||
/// An infinite rectangle should be interpreted as "as much as you want".
|
||||
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
|
||||
pub fn available(&self) -> Rect {
|
||||
self.layout.available(self.cursor, self.max_rect())
|
||||
/// A small size should be interpreted as "as little as possible".
|
||||
/// An infinite size should be interpreted as "as much as you want".
|
||||
pub fn available_size(&self) -> Vec2 {
|
||||
self.layout.available_size(&self.region)
|
||||
}
|
||||
|
||||
/// This is like `available()`, but will never be infinite.
|
||||
pub fn available_width(&self) -> f32 {
|
||||
self.available_size().x
|
||||
}
|
||||
|
||||
/// In case of a wrapping layout, how much space is left on this row/column?
|
||||
pub fn available_size_before_wrap(&self) -> Vec2 {
|
||||
self.layout.available_size_before_wrap(&self.region)
|
||||
}
|
||||
|
||||
/// 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).
|
||||
/// In most layouts the next widget will be put in the top left corner of this `Rect`.
|
||||
pub fn available_finite(&self) -> Rect {
|
||||
self.layout.available(self.cursor, self.max_rect_finite())
|
||||
pub fn available_size_before_wrap_finite(&self) -> Vec2 {
|
||||
self.layout
|
||||
.available_rect_before_wrap_finite(&self.region)
|
||||
.size()
|
||||
}
|
||||
|
||||
pub fn available_rect_before_wrap(&self) -> Rect {
|
||||
self.layout.available_rect_before_wrap(&self.region)
|
||||
}
|
||||
|
||||
/// 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).
|
||||
/// 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 {
|
||||
self.layout.available_rect_before_wrap_finite(&self.region)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,7 +402,7 @@ impl Ui {
|
|||
/// The direction is dependent on the layout.
|
||||
/// This is useful for creating some extra space between widgets.
|
||||
pub fn advance_cursor(&mut self, amount: f32) {
|
||||
self.layout.advance_cursor(&mut self.cursor, amount);
|
||||
self.layout.advance_cursor(&mut self.region, amount);
|
||||
}
|
||||
|
||||
/// Reserve this much space and move the cursor.
|
||||
|
@ -429,16 +414,16 @@ impl Ui {
|
|||
/// If you want to fill the space, ask about `available().size()` and use that.
|
||||
///
|
||||
/// You may get MORE space than you asked for, for instance
|
||||
/// for `Justified` aligned layouts, like in menus.
|
||||
/// for justified layouts, like in menus.
|
||||
///
|
||||
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
|
||||
pub fn allocate_space(&mut self, desired_size: Vec2) -> Rect {
|
||||
// For debug rendering
|
||||
let original_size = self.available().size();
|
||||
let too_wide = desired_size.x > original_size.x;
|
||||
let too_high = desired_size.y > original_size.y;
|
||||
let original_available = self.available_size_before_wrap();
|
||||
let too_wide = desired_size.x > original_available.x;
|
||||
let too_high = desired_size.y > original_available.y;
|
||||
|
||||
let rect = self.reserve_space_impl(desired_size);
|
||||
let rect = self.allocate_space_impl(desired_size);
|
||||
|
||||
let debug_expand_width = self.style().visuals.debug_expand_width;
|
||||
let debug_expand_height = self.style().visuals.debug_expand_height;
|
||||
|
@ -455,8 +440,8 @@ impl Ui {
|
|||
paint_line_seg(rect.left_top(), rect.left_bottom());
|
||||
paint_line_seg(rect.left_center(), rect.right_center());
|
||||
paint_line_seg(
|
||||
pos2(rect.left() + original_size.x, rect.top()),
|
||||
pos2(rect.left() + original_size.x, rect.bottom()),
|
||||
pos2(rect.left() + original_available.x, rect.top()),
|
||||
pos2(rect.left() + original_available.x, rect.bottom()),
|
||||
);
|
||||
paint_line_seg(rect.right_top(), rect.right_bottom());
|
||||
}
|
||||
|
@ -473,16 +458,65 @@ impl Ui {
|
|||
|
||||
/// Reserve this much space and move the cursor.
|
||||
/// Returns where to put the widget.
|
||||
fn reserve_space_impl(&mut self, child_size: Vec2) -> Rect {
|
||||
let available_size = self.available_finite().size();
|
||||
let child_rect = self
|
||||
.layout
|
||||
.allocate_space(&mut self.cursor, available_size, child_size);
|
||||
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
|
||||
let item_spacing = self.style().spacing.item_spacing;
|
||||
self.layout.advance_cursor2(&mut self.cursor, item_spacing);
|
||||
self.expand_to_include_rect(child_rect);
|
||||
let outer_child_rect = self
|
||||
.layout
|
||||
.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(
|
||||
&mut self.region,
|
||||
outer_child_rect,
|
||||
inner_child_rect,
|
||||
item_spacing,
|
||||
);
|
||||
self.region.expand_to_include_rect(inner_child_rect);
|
||||
|
||||
self.next_auto_id = self.next_auto_id.wrapping_add(1);
|
||||
child_rect
|
||||
inner_child_rect
|
||||
}
|
||||
|
||||
pub(crate) fn advance_cursor_after_rect(&mut self, rect: Rect) {
|
||||
let item_spacing = self.style().spacing.item_spacing;
|
||||
self.layout
|
||||
.advance_after_outer_rect(&mut self.region, rect, rect, item_spacing);
|
||||
self.region.expand_to_include_rect(rect);
|
||||
}
|
||||
|
||||
pub(crate) fn cursor(&self) -> Pos2 {
|
||||
self.region.cursor
|
||||
}
|
||||
|
||||
/// Allocated the given space and then adds content to that space.
|
||||
/// 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<R>(
|
||||
&mut self,
|
||||
desired_size: Vec2,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> (R, Response) {
|
||||
let item_spacing = self.style().spacing.item_spacing;
|
||||
let outer_child_rect = self
|
||||
.layout
|
||||
.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 ret = add_contents(&mut child_ui);
|
||||
let final_child_rect = child_ui.region.min_rect;
|
||||
|
||||
self.layout.advance_after_outer_rect(
|
||||
&mut self.region,
|
||||
outer_child_rect.union(final_child_rect),
|
||||
final_child_rect,
|
||||
item_spacing,
|
||||
);
|
||||
self.region.expand_to_include_rect(final_child_rect);
|
||||
|
||||
let response = self.interact_hover(final_child_rect);
|
||||
(ret, response)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -532,12 +566,24 @@ impl Ui {
|
|||
self.add(TextEdit::multiline(text))
|
||||
}
|
||||
|
||||
/// Usage: `if ui.button("Click me").clicked { ... }`
|
||||
///
|
||||
/// Shortcut for `add(Button::new(text))`
|
||||
#[must_use = "You should check if the user clicked this with `if ui.button(...).clicked { ... } "]
|
||||
pub fn button(&mut self, text: impl Into<String>) -> Response {
|
||||
self.add(Button::new(text))
|
||||
}
|
||||
|
||||
/// A button as small as normal body text.
|
||||
///
|
||||
/// Usage: `if ui.small_button("Click me").clicked { ... }`
|
||||
///
|
||||
/// Shortcut for `add(Button::new(text).small())`
|
||||
#[must_use = "You should check if the user clicked this with `if ui.small_button(...).clicked { ... } "]
|
||||
pub fn small_button(&mut self, text: impl Into<String>) -> Response {
|
||||
self.add(Button::new(text).small())
|
||||
}
|
||||
|
||||
/// Show a checkbox.
|
||||
pub fn checkbox(&mut self, checked: &mut bool, text: impl Into<String>) -> Response {
|
||||
self.add(Checkbox::new(checked, text))
|
||||
|
@ -676,7 +722,7 @@ impl Ui {
|
|||
impl Ui {
|
||||
/// 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) {
|
||||
let child_rect = self.available();
|
||||
let child_rect = self.available_rect_before_wrap();
|
||||
let mut child_ui = self.child_ui(child_rect, self.layout);
|
||||
let ret = add_contents(&mut child_ui);
|
||||
let size = child_ui.min_size();
|
||||
|
@ -696,19 +742,13 @@ impl Ui {
|
|||
})
|
||||
}
|
||||
|
||||
/// Create a child ui at the current cursor.
|
||||
/// `size` is the desired size.
|
||||
/// Actual size may be much smaller if `available_size()` is not enough.
|
||||
/// Set `size` to `Vec::infinity()` to get as much space as possible.
|
||||
/// Just because you ask for a lot of space does not mean you have to use it!
|
||||
/// After `add_contents` is called the contents of `min_size`
|
||||
/// will decide how much space will be used in the parent ui.
|
||||
pub fn add_custom_contents(&mut self, size: Vec2, add_contents: impl FnOnce(&mut Ui)) -> Rect {
|
||||
let size = size.at_most(self.available().size());
|
||||
let child_rect = self.layout.rect_from_cursor_size(self.cursor, size);
|
||||
let mut child_ui = self.child_ui(child_rect, self.layout);
|
||||
add_contents(&mut child_ui);
|
||||
self.allocate_space(child_ui.min_size())
|
||||
#[deprecated = "Use `ui.allocate_ui` instead"]
|
||||
pub fn add_custom_contents(
|
||||
&mut self,
|
||||
desired_size: Vec2,
|
||||
add_contents: impl FnOnce(&mut Ui),
|
||||
) -> Rect {
|
||||
self.allocate_ui(desired_size, add_contents).1.rect
|
||||
}
|
||||
|
||||
/// A `CollapsingHeader` that starts out collapsed.
|
||||
|
@ -727,11 +767,13 @@ impl Ui {
|
|||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> (R, Response) {
|
||||
assert!(
|
||||
self.layout().dir() == Direction::Vertical,
|
||||
"You can only indent vertical layouts"
|
||||
self.layout.is_vertical(),
|
||||
"You can only indent vertical layouts, found {:?}",
|
||||
self.layout
|
||||
);
|
||||
let indent = vec2(self.style().spacing.indent, 0.0);
|
||||
let child_rect = Rect::from_min_max(self.cursor + indent, self.max_rect.right_bottom()); // TODO: wrong for reversed layouts
|
||||
let child_rect =
|
||||
Rect::from_min_max(self.region.cursor + indent, self.max_rect().right_bottom()); // TODO: wrong for reversed layouts
|
||||
let mut child_ui = Self {
|
||||
id: self.id.with(id_source),
|
||||
..self.child_ui(child_rect, self.layout)
|
||||
|
@ -768,64 +810,102 @@ impl Ui {
|
|||
pub fn column(&mut self, column_position: Align, width: f32) -> Self {
|
||||
let x = match column_position {
|
||||
Align::Min => 0.0,
|
||||
Align::Center => self.available().width() / 2.0 - width / 2.0,
|
||||
Align::Max => self.available().width() - width,
|
||||
Align::Center => self.available_width() / 2.0 - width / 2.0,
|
||||
Align::Max => self.available_width() - width,
|
||||
};
|
||||
self.child_ui(
|
||||
Rect::from_min_size(
|
||||
self.cursor + vec2(x, 0.0),
|
||||
vec2(width, self.available().height()),
|
||||
self.region.cursor + vec2(x, 0.0),
|
||||
vec2(width, self.available_size_before_wrap().y),
|
||||
),
|
||||
self.layout,
|
||||
)
|
||||
}
|
||||
|
||||
/// Start a ui with horizontal layout.
|
||||
/// After you have called this, the registers the contents as any other widget.
|
||||
/// After you have called this, the function registers the contents as any other widget.
|
||||
///
|
||||
/// Elements will be centered on the Y axis, i.e.
|
||||
/// adjusted up and down to lie in the center of the horizontal layout.
|
||||
/// The initial height is `style.spacing.interact_size.y`.
|
||||
/// Centering is almost always what you want if you are
|
||||
/// planning to to mix widgets or just different types of text.
|
||||
/// planning to to mix widgets or use different types of text.
|
||||
///
|
||||
/// The returned `Response` will only have checked for mouse hover
|
||||
/// but can be used for tooltips (`on_hover_text`).
|
||||
/// It also contains the `Rect` used by the horizontal layout.
|
||||
pub fn horizontal<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
|
||||
self.horizontal_with_main_wrap(false, add_contents)
|
||||
}
|
||||
|
||||
/// Start a ui with horizontal layout that wraps to a new row
|
||||
/// when it reaches the right edge of the `max_size`.
|
||||
/// After you have called this, the function registers the contents as any other widget.
|
||||
///
|
||||
/// Elements will be centered on the Y axis, i.e.
|
||||
/// adjusted up and down to lie in the center of the horizontal layout.
|
||||
/// The initial height is `style.spacing.interact_size.y`.
|
||||
/// Centering is almost always what you want if you are
|
||||
/// planning to to mix widgets or use different types of text.
|
||||
///
|
||||
/// The returned `Response` will only have checked for mouse hover
|
||||
/// but can be used for tooltips (`on_hover_text`).
|
||||
/// It also contains the `Rect` used by the horizontal layout.
|
||||
pub fn horizontal_wrapped<R>(
|
||||
&mut self,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> (R, Response) {
|
||||
self.horizontal_with_main_wrap(true, add_contents)
|
||||
}
|
||||
|
||||
/// Like `horizontal_wrapped`, but will set up spacing so that
|
||||
/// the line size and matches that for a normal label.
|
||||
///
|
||||
/// In particular, the space between widgets is the same with as the space character
|
||||
/// and the line spacing is the same as that for text.
|
||||
///
|
||||
/// You can still add any widgets to the layout (not only Labels).
|
||||
pub fn horizontal_wrapped_for_text<R>(
|
||||
&mut self,
|
||||
text_style: TextStyle,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> (R, Response) {
|
||||
self.wrap(|ui| {
|
||||
let font = &ui.fonts()[text_style];
|
||||
let row_height = font.row_height();
|
||||
let space_width = font.glyph_width(' ');
|
||||
let style = ui.style_mut();
|
||||
style.spacing.interact_size.y = row_height;
|
||||
style.spacing.item_spacing.x = space_width;
|
||||
style.spacing.item_spacing.y = 0.0;
|
||||
ui.horizontal_wrapped(add_contents).0
|
||||
})
|
||||
}
|
||||
|
||||
fn horizontal_with_main_wrap<R>(
|
||||
&mut self,
|
||||
main_wrap: bool,
|
||||
add_contents: impl FnOnce(&mut Ui) -> R,
|
||||
) -> (R, Response) {
|
||||
let initial_size = vec2(
|
||||
self.available().width(),
|
||||
self.available_size_before_wrap_finite().x,
|
||||
self.style().spacing.interact_size.y, // Assume there will be something interactive on the horizontal layout
|
||||
);
|
||||
|
||||
let right_to_left =
|
||||
(self.layout.dir(), self.layout.align()) == (Direction::Vertical, Some(Align::Max));
|
||||
let layout = if self.layout.prefer_right_to_left() {
|
||||
Layout::right_to_left()
|
||||
} else {
|
||||
Layout::left_to_right()
|
||||
}
|
||||
.with_main_wrap(main_wrap);
|
||||
|
||||
self.inner_layout(
|
||||
Layout::horizontal(Align::Center).with_reversed(right_to_left),
|
||||
initial_size,
|
||||
add_contents,
|
||||
)
|
||||
self.allocate_ui(initial_size, |ui| ui.with_layout(layout, add_contents).0)
|
||||
}
|
||||
|
||||
/// Start a ui with vertical layout.
|
||||
/// Widgets will be left-justified.
|
||||
pub fn vertical<R>(&mut self, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) {
|
||||
self.with_layout(Layout::vertical(Align::Min), add_contents)
|
||||
}
|
||||
|
||||
fn inner_layout<R>(
|
||||
&mut self,
|
||||
layout: Layout,
|
||||
initial_size: Vec2,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> (R, Response) {
|
||||
let child_rect = self.layout.rect_from_cursor_size(self.cursor, initial_size);
|
||||
let mut child_ui = self.child_ui(child_rect, layout);
|
||||
let ret = add_contents(&mut child_ui);
|
||||
let size = child_ui.min_size();
|
||||
let rect = self.allocate_space(size);
|
||||
(ret, self.interact_hover(rect))
|
||||
self.with_layout(Layout::top_down(Align::Min), add_contents)
|
||||
}
|
||||
|
||||
pub fn with_layout<R>(
|
||||
|
@ -833,10 +913,13 @@ impl Ui {
|
|||
layout: Layout,
|
||||
add_contents: impl FnOnce(&mut Self) -> R,
|
||||
) -> (R, Response) {
|
||||
let mut child_ui = self.child_ui(self.available(), layout);
|
||||
let mut child_ui = self.child_ui(self.available_rect_before_wrap(), layout);
|
||||
let ret = add_contents(&mut child_ui);
|
||||
let size = child_ui.min_size();
|
||||
let rect = self.allocate_space(size);
|
||||
let rect = child_ui.min_rect();
|
||||
let item_spacing = self.style().spacing.item_spacing;
|
||||
self.layout
|
||||
.advance_after_outer_rect(&mut self.region, rect, rect, item_spacing);
|
||||
self.region.expand_to_include_rect(rect);
|
||||
(ret, self.interact_hover(rect))
|
||||
}
|
||||
|
||||
|
@ -856,14 +939,15 @@ impl Ui {
|
|||
// TODO: ensure there is space
|
||||
let spacing = self.style().spacing.item_spacing.x;
|
||||
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 mut columns: Vec<Self> = (0..num_columns)
|
||||
.map(|col_idx| {
|
||||
let pos = self.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0);
|
||||
let pos =
|
||||
self.region.cursor + vec2((col_idx as f32) * (column_width + spacing), 0.0);
|
||||
let child_rect = Rect::from_min_max(
|
||||
pos,
|
||||
pos2(pos.x + column_width, self.max_rect.right_bottom().y),
|
||||
pos2(pos.x + column_width, self.max_rect().right_bottom().y),
|
||||
);
|
||||
self.child_ui(child_rect, self.layout)
|
||||
})
|
||||
|
@ -873,7 +957,7 @@ impl Ui {
|
|||
|
||||
let mut sum_width = total_spacing;
|
||||
for column in &columns {
|
||||
sum_width += column.min_rect.width();
|
||||
sum_width += column.min_rect().width();
|
||||
}
|
||||
|
||||
let mut max_height = 0.0;
|
||||
|
@ -882,7 +966,7 @@ impl Ui {
|
|||
max_height = size.y.max(max_height);
|
||||
}
|
||||
|
||||
let size = vec2(self.available().width().max(sum_width), max_height);
|
||||
let size = vec2(self.available_width().max(sum_width), max_height);
|
||||
self.allocate_space(size);
|
||||
result
|
||||
}
|
||||
|
@ -894,6 +978,6 @@ impl Ui {
|
|||
impl Ui {
|
||||
/// Shows where the next widget is going to be placed
|
||||
pub fn debug_paint_cursor(&self) {
|
||||
self.layout.debug_paint_cursor(self.cursor, &self.painter);
|
||||
self.layout.debug_paint_cursor(&self.region, &self.painter);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#![allow(clippy::new_without_default)]
|
||||
|
||||
use crate::{layout::Direction, *};
|
||||
use crate::*;
|
||||
|
||||
pub mod color_picker;
|
||||
mod drag_value;
|
||||
|
@ -79,10 +79,7 @@ impl Label {
|
|||
}
|
||||
|
||||
pub fn layout(&self, ui: &Ui) -> Galley {
|
||||
let max_width = ui.available().width();
|
||||
// Prevent word-wrapping after a single letter, and other silly shit:
|
||||
// TODO: general "don't force labels and similar to wrap so early"
|
||||
// TODO: max_width = max_width.at_least(ui.spacing.first_wrap_width);
|
||||
let max_width = ui.available_width();
|
||||
self.layout_width(ui, max_width)
|
||||
}
|
||||
|
||||
|
@ -125,11 +122,58 @@ impl Label {
|
|||
|
||||
impl Widget for Label {
|
||||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
if self.multiline
|
||||
&& ui.layout().main_dir() == Direction::LeftToRight
|
||||
&& ui.layout().main_wrap()
|
||||
{
|
||||
// On a wrapping horizontal layout we want text to start after the last widget,
|
||||
// then continue on the line below! This will take some extra work:
|
||||
|
||||
let max_width = ui.available_width();
|
||||
let first_row_indentation = max_width - ui.available_size_before_wrap().x;
|
||||
|
||||
let text_style = self.text_style_or_default(ui.style());
|
||||
let font = &ui.fonts()[text_style];
|
||||
let mut galley = font.layout_multiline_with_indentation_and_max_width(
|
||||
self.text.clone(),
|
||||
first_row_indentation,
|
||||
max_width,
|
||||
);
|
||||
|
||||
let pos = pos2(ui.min_rect().left(), ui.cursor().y);
|
||||
|
||||
assert!(!galley.rows.is_empty(), "Gallyes are never empty");
|
||||
let rect = galley.rows[0].rect().translate(vec2(pos.x, pos.y));
|
||||
ui.advance_cursor_after_rect(rect);
|
||||
let mut total_response = ui.interact_hover(rect);
|
||||
|
||||
let mut y_translation = 0.0;
|
||||
if let Some(row) = galley.rows.get(1) {
|
||||
// We could be sharing the first row with e.g. a button, that is higher than text.
|
||||
// So we need to compensate for that:
|
||||
if pos.y + row.y_min < ui.min_rect().bottom() {
|
||||
y_translation = ui.min_rect().bottom() - row.y_min - pos.y;
|
||||
}
|
||||
}
|
||||
|
||||
for row in galley.rows.iter_mut().skip(1) {
|
||||
row.y_min += y_translation;
|
||||
row.y_max += y_translation;
|
||||
let rect = row.rect().translate(vec2(pos.x, pos.y));
|
||||
ui.advance_cursor_after_rect(rect);
|
||||
total_response |= ui.interact_hover(rect);
|
||||
}
|
||||
|
||||
self.paint_galley(ui, pos, galley);
|
||||
total_response
|
||||
} else {
|
||||
let galley = self.layout(ui);
|
||||
let rect = ui.allocate_space(galley.size);
|
||||
let rect = ui.layout().align_size_within_rect(galley.size, rect);
|
||||
self.paint_galley(ui, rect.min, galley);
|
||||
ui.interact_hover(rect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Label> for &str {
|
||||
|
@ -197,7 +241,7 @@ impl Widget for Hyperlink {
|
|||
let color = color::LIGHT_BLUE;
|
||||
let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style);
|
||||
let font = &ui.fonts()[text_style];
|
||||
let galley = font.layout_multiline(text, ui.available().width());
|
||||
let galley = font.layout_multiline(text, ui.available_width());
|
||||
let rect = ui.allocate_space(galley.size);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
|
@ -243,6 +287,7 @@ pub struct Button {
|
|||
/// None means default for interact
|
||||
fill: Option<Srgba>,
|
||||
sense: Sense,
|
||||
small: bool,
|
||||
}
|
||||
|
||||
impl Button {
|
||||
|
@ -253,6 +298,7 @@ impl Button {
|
|||
text_style: TextStyle::Button,
|
||||
fill: Default::default(),
|
||||
sense: Sense::click(),
|
||||
small: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,6 +322,13 @@ impl Button {
|
|||
self
|
||||
}
|
||||
|
||||
/// Make this a small button, suitable for embedding into text.
|
||||
pub fn small(mut self) -> Self {
|
||||
self.text_style = TextStyle::Body;
|
||||
self.small = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// By default, buttons senses clicks.
|
||||
/// Change this to a drag-button with `Sense::drag()`.
|
||||
pub fn sense(mut self, sense: Sense) -> Self {
|
||||
|
@ -301,24 +354,29 @@ impl Widget for Button {
|
|||
text_style,
|
||||
fill,
|
||||
sense,
|
||||
small,
|
||||
} = self;
|
||||
|
||||
let button_padding = ui.style().spacing.button_padding;
|
||||
let mut button_padding = ui.style().spacing.button_padding;
|
||||
if small {
|
||||
button_padding.y = 0.0;
|
||||
}
|
||||
|
||||
let font = &ui.fonts()[text_style];
|
||||
let galley = font.layout_multiline(text, ui.available().width());
|
||||
let galley = font.layout_multiline(text, ui.available_width());
|
||||
let mut desired_size = galley.size + 2.0 * button_padding;
|
||||
if !small {
|
||||
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
|
||||
}
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, sense);
|
||||
let visuals = ui.style().interact(&response);
|
||||
// let text_cursor = response.rect.center() - 0.5 * galley.size; // centered-centered (looks bad for justified drop-down menus
|
||||
let text_cursor = pos2(
|
||||
response.rect.left() + button_padding.x,
|
||||
response.rect.center().y - 0.5 * galley.size.y,
|
||||
); // left-centered
|
||||
let text_cursor = ui
|
||||
.layout()
|
||||
.align_size_within_rect(galley.size, response.rect.shrink2(button_padding))
|
||||
.min;
|
||||
let fill = fill.unwrap_or(visuals.bg_fill);
|
||||
ui.painter().rect(
|
||||
response.rect,
|
||||
|
@ -379,12 +437,13 @@ impl<'a> Widget for Checkbox<'a> {
|
|||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||
|
||||
let galley = font.layout_single_line(text);
|
||||
// let galley = font.layout_multiline(text, ui.available().width() - total_extra.x);
|
||||
// let galley = font.layout_multiline(text, ui.available_width() - total_extra.x);
|
||||
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(spacing.interact_size);
|
||||
desired_size.y = desired_size.y.max(icon_width);
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
let rect = ui.layout().align_size_within_rect(desired_size, rect);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, Sense::click());
|
||||
|
@ -467,12 +526,13 @@ impl Widget for RadioButton {
|
|||
let button_padding = ui.style().spacing.button_padding;
|
||||
let total_extra = button_padding + vec2(icon_width + icon_spacing, 0.0) + button_padding;
|
||||
|
||||
let galley = font.layout_multiline(text, ui.available().width() - total_extra.x);
|
||||
let galley = font.layout_multiline(text, ui.available_width() - total_extra.x);
|
||||
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
|
||||
desired_size.y = desired_size.y.max(icon_width);
|
||||
let rect = ui.allocate_space(desired_size);
|
||||
let rect = ui.layout().align_size_within_rect(desired_size, rect);
|
||||
|
||||
let id = ui.make_position_id();
|
||||
let response = ui.interact(rect, id, Sense::click());
|
||||
|
@ -544,7 +604,7 @@ impl Widget for SelectableLabel {
|
|||
let button_padding = ui.style().spacing.button_padding;
|
||||
let total_extra = button_padding + button_padding;
|
||||
|
||||
let galley = font.layout_multiline(text, ui.available().width() - total_extra.x);
|
||||
let galley = font.layout_multiline(text, ui.available_width() - total_extra.x);
|
||||
|
||||
let mut desired_size = total_extra + galley.size;
|
||||
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
|
||||
|
@ -604,10 +664,9 @@ impl Widget for Separator {
|
|||
fn ui(self, ui: &mut Ui) -> Response {
|
||||
let Separator { spacing } = self;
|
||||
|
||||
let available_space = ui.available_finite().size();
|
||||
let available_space = ui.available_size_before_wrap_finite();
|
||||
|
||||
let (points, rect) = match ui.layout().dir() {
|
||||
Direction::Horizontal => {
|
||||
let (points, rect) = if ui.layout().main_dir().is_horizontal() {
|
||||
let rect = ui.allocate_space(vec2(spacing, available_space.y));
|
||||
(
|
||||
[
|
||||
|
@ -616,8 +675,7 @@ impl Widget for Separator {
|
|||
],
|
||||
rect,
|
||||
)
|
||||
}
|
||||
Direction::Vertical => {
|
||||
} else {
|
||||
let rect = ui.allocate_space(vec2(available_space.x, spacing));
|
||||
(
|
||||
[
|
||||
|
@ -626,7 +684,6 @@ impl Widget for Separator {
|
|||
],
|
||||
rect,
|
||||
)
|
||||
}
|
||||
};
|
||||
let stroke = ui.style().visuals.widgets.noninteractive.bg_stroke;
|
||||
ui.painter().line_segment(points, stroke);
|
||||
|
|
|
@ -228,7 +228,7 @@ impl<'t> Widget for TextEdit<'t> {
|
|||
let text_style = text_style.unwrap_or_else(|| ui.style().body_text_style);
|
||||
let font = &ui.fonts()[text_style];
|
||||
let line_spacing = font.row_height();
|
||||
let available_width = ui.available().width();
|
||||
let available_width = ui.available_width();
|
||||
let mut galley = if multiline {
|
||||
font.layout_multiline(text.clone(), available_width)
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue