Merge pull request #60 from emilk/layout-wrapping

Wrapping layouts
This commit is contained in:
Emil Ernerfeldt 2020-12-10 23:38:54 +01:00 committed by GitHub
commit 884558ac48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1031 additions and 555 deletions

View file

@ -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 🔥

View file

@ -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

View file

@ -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;

View file

@ -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());

View file

@ -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());

View file

@ -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)

View file

@ -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(

View file

@ -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 {

View file

@ -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![];

View file

@ -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");
}
}
}

View file

@ -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

View file

@ -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());

View file

@ -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 {

View file

@ -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.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");

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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,
);
})
});

View file

@ -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(),

View file

@ -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 {

View file

@ -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);
}
}

View file

@ -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,12 +122,59 @@ 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 {
fn into(self) -> Label {
@ -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);

View file

@ -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 {