fix cursor advancement after wrapping

This commit is contained in:
Emil Ernerfeldt 2020-12-08 21:18:33 +01:00
parent ea9133a92d
commit 5cd07db96a
9 changed files with 189 additions and 109 deletions

View file

@ -103,7 +103,7 @@ impl ScrollArea {
};
let outer_size = vec2(
ui.available().width(),
ui.available_width(),
ui.available().height().at_most(max_height),
);

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

@ -323,7 +323,7 @@ impl Default for LayoutDemo {
cross_align: Align::Min,
cross_justify: false,
wrap_column_width: 150.0,
wrap_row_height: 40.0,
wrap_row_height: 20.0,
}
}
}
@ -338,7 +338,7 @@ impl LayoutDemo {
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| {
if self.main_wrap {
if self.main_dir.is_horizontal() {
@ -360,10 +360,22 @@ impl LayoutDemo {
}
pub fn content_ui(&mut self, ui: &mut Ui) {
// ui.label(format!("Available space: {:?}", ui.available().size()));
if ui.button("Default").clicked {
*self = Default::default();
}
ui.horizontal(|ui| {
if ui.button("Top-down").clicked {
*self = Default::default();
}
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;
}
});
ui.horizontal(|ui| {
ui.label("Main Direction:");
@ -400,9 +412,20 @@ impl LayoutDemo {
}
pub fn demo_ui(&mut self, ui: &mut Ui) {
ui.heading("Effect:");
for i in 0..7 {
let _ = ui.button(format!("Button {}", i));
ui.monospace("Example widgets:");
for i in 0..9 {
match i % 3 {
0 => {
ui.label(format!("{} label", i));
}
1 => {
let mut dummy = false;
ui.checkbox(&mut dummy, format!("{} checkbox", i));
}
_ => {
let _ = ui.button(format!("{} button", i));
}
}
}
}
}

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

@ -282,10 +282,31 @@ impl Layout {
}
}
pub fn available(&self, region: &Region) -> Rect {
// TODO: clarify if it is before or after wrap
pub(crate) fn available(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect)
}
/// 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 {
vec2(r.max_rect.right() - r.cursor.x, r.max_rect.height())
}
} else {
self.available_from_cursor_max_rect(r.cursor, r.max_rect)
.size()
}
}
fn available_size_before_wrap(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect)
}
// TODO
pub fn available_finite(&self, region: &Region) -> Rect {
self.available_from_cursor_max_rect(region.cursor, region.max_rect_finite())
}
@ -317,97 +338,106 @@ impl Layout {
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 layouts, like in menus.
///
/// You may get LESS space than you asked for if the current layout won't fit what you asked for.
/// 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, minimum_child_size: Vec2) -> Rect {
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_finite(region).size();
// TODO: spacing?
let available_size = self.available_size_before_wrap(region).size();
match self.main_dir {
Direction::LeftToRight => {
if available_size.x < minimum_child_size.x && region.max_rect.left() < cursor.x
{
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());
cursor = pos2(
region.max_rect.left(),
region.max_rect.bottom() + item_spacing.y,
);
}
}
Direction::RightToLeft => {
if available_size.x < minimum_child_size.x && cursor.x < region.max_rect.right()
{
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());
cursor = pos2(
region.max_rect.right(),
region.max_rect.bottom() + item_spacing.y,
);
}
}
Direction::TopDown => {
if available_size.y < minimum_child_size.y && region.max_rect.top() < cursor.y {
if available_size.y < child_size.y && region.max_rect.top() < cursor.y {
// New column
cursor = pos2(region.max_rect.right(), region.max_rect.top());
cursor = pos2(
region.max_rect.right() + item_spacing.x,
region.max_rect.top(),
);
}
}
Direction::BottomUp => {
if available_size.y < minimum_child_size.y
&& cursor.y < region.max_rect.bottom()
{
if available_size.y < child_size.y && cursor.y < region.max_rect.bottom() {
// New column
cursor = pos2(region.max_rect.right(), region.max_rect.bottom());
cursor = pos2(
region.max_rect.right() + item_spacing.x,
region.max_rect.bottom(),
);
}
}
}
}
let available_size = self.available_finite(region).size();
let available_size = available_size.at_least(minimum_child_size);
let mut child_size = minimum_child_size;
let mut child_move = Vec2::default();
if self.main_dir.is_horizontal() {
if self.cross_justify {
// fill full height
child_size.y = child_size.y.max(available_size.y);
} else {
child_move.y += match self.cross_align {
Align::Min => 0.0,
Align::Center => 0.5 * (available_size.y - child_size.y),
Align::Max => available_size.y - child_size.y,
};
}
// Fill full height
child_size.y = child_size.y.max(available_size.y);
} else {
if self.cross_justify {
// justified: fill full width
child_size.x = child_size.x.max(available_size.x);
} else {
child_move.x += match self.cross_align {
Align::Min => 0.0,
Align::Center => 0.5 * (available_size.x - child_size.x),
Align::Max => available_size.x - child_size.x,
};
}
// Fill full width
child_size.x = child_size.x.max(available_size.x);
}
let child_pos = match self.main_dir {
Direction::LeftToRight => cursor + child_move,
Direction::RightToLeft => cursor + child_move + vec2(-child_size.x, 0.0),
Direction::TopDown => cursor + child_move,
Direction::BottomUp => cursor + child_move + vec2(0.0, -child_size.y),
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, region: &mut Region, amount: f32) {
match self.main_dir {
@ -428,7 +458,7 @@ impl Layout {
}
/// Advance cursor after a widget was added to a specific rectangle.
pub fn advance_after_rect(self, region: &mut Region, rect: Rect, item_spacing: Vec2) {
pub fn advance_after_outer_rect(self, region: &mut Region, rect: Rect, item_spacing: Vec2) {
region.cursor = match self.main_dir {
Direction::LeftToRight => pos2(rect.right() + item_spacing.x, rect.top()),
Direction::RightToLeft => pos2(rect.left() - item_spacing.x, rect.top()),

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

View file

@ -313,9 +313,17 @@ 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`.
/// 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)
}
pub fn available_width(&self) -> f32 {
self.available_size().x
}
// TODO: clarify if this is before or after wrap
pub fn available(&self) -> Rect {
self.layout.available(&self.region)
}
@ -438,14 +446,17 @@ impl Ui {
/// Reserve this much space and move the cursor.
/// Returns where to put the widget.
fn allocate_space_impl(&mut self, desired_size: Vec2) -> Rect {
let child_rect = self.layout.next_space(&self.region, desired_size);
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);
self.layout
.advance_after_rect(&mut self.region, child_rect, item_spacing);
.advance_after_outer_rect(&mut self.region, outer_child_rect, item_spacing);
self.next_auto_id = self.next_auto_id.wrapping_add(1);
child_rect
inner_child_rect
}
/// Allocated the given space and then adds content to that space.
@ -457,16 +468,23 @@ impl Ui {
desired_size: Vec2,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> (R, Response) {
let child_rect = self.layout.next_space(&self.region, desired_size);
let mut child_ui = self.child_ui(child_rect, self.layout);
let ret = add_contents(&mut child_ui);
let child_rect = child_ui.region.max_rect;
let item_spacing = self.style().spacing.item_spacing;
self.layout
.advance_after_rect(&mut self.region, child_rect, 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 response = self.interact_hover(child_rect);
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.max_rect;
self.layout.advance_after_outer_rect(
&mut self.region,
outer_child_rect.union(final_child_rect),
item_spacing,
);
let response = self.interact_hover(final_child_rect);
(ret, response)
}
@ -476,19 +494,26 @@ impl Ui {
/// So you can request a lot of space and then use less.
pub fn allocate_ui_min<R>(
&mut self,
initial_size: Vec2,
desired_size: Vec2,
add_contents: impl FnOnce(&mut Self) -> R,
) -> (R, Response) {
let child_rect = self.layout.next_space(&self.region, initial_size);
let mut child_ui = self.child_ui(child_rect, self.layout);
let ret = add_contents(&mut child_ui);
let child_rect = child_ui.region.min_rect;
let item_spacing = self.style().spacing.item_spacing;
self.layout
.advance_after_rect(&mut self.region, child_rect, 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 response = self.interact_hover(child_rect);
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),
item_spacing,
);
let response = self.interact_hover(final_child_rect);
(ret, response)
}
}
@ -771,8 +796,8 @@ 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(
@ -823,8 +848,10 @@ impl Ui {
) -> (R, Response) {
let mut child_ui = self.child_ui(self.available(), 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, item_spacing);
(ret, self.interact_hover(rect))
}
@ -844,7 +871,7 @@ 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| {
@ -871,7 +898,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
}

View file

@ -79,7 +79,7 @@ impl Label {
}
pub fn layout(&self, ui: &Ui) -> Galley {
let max_width = ui.available().width();
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);
@ -198,7 +198,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();
@ -307,7 +307,7 @@ impl Widget for Button {
let button_padding = ui.style().spacing.button_padding;
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;
desired_size = desired_size.at_least(ui.style().spacing.interact_size);
let rect = ui.allocate_space(desired_size);
@ -379,7 +379,7 @@ 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);
@ -467,7 +467,7 @@ 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);
@ -543,7 +543,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);

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 {