Skip to content

Commit

Permalink
Fix some edge cases and allow widgets to grow / unwrap if they were p…
Browse files Browse the repository at this point in the history
…reviously wrapped
  • Loading branch information
lucasmerlin committed Sep 20, 2024
1 parent f77223f commit 8b9fcd6
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 23 deletions.
14 changes: 10 additions & 4 deletions crates/egui_flex/examples/flex_example.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use eframe::NativeOptions;
use egui::{
Area, Button, CentralPanel, Checkbox, Frame, Id, Label, Slider, TextEdit, Widget, Window,
};
use egui::{Button, CentralPanel, Checkbox, Frame, Id, Label, Slider, TextEdit, Widget};
use egui_flex::{Flex, FlexAlign, FlexItem};
use std::num::NonZeroUsize;

fn main() -> eframe::Result {
let mut text = "Hello, world!".to_owned();
Expand All @@ -11,6 +10,10 @@ fn main() -> eframe::Result {
NativeOptions::default(),
move |ctx, _frame| {
CentralPanel::default().show(ctx, |ui| {
ctx.options_mut(|opts| {
opts.max_passes = NonZeroUsize::new(3).unwrap();
});

ui.horizontal_top(|ui| {
let items = vec![
"I",
Expand Down Expand Up @@ -89,7 +92,10 @@ fn main() -> eframe::Result {
FlexItem::default().grow(1.0).align_self(*align),
Frame::group(flex.ui().style()),
|ui| {
ui.label(format!("I have align-self: {:?}", align));
ui.add(
Label::new(format!("I have align-self: {:?}", align))
.wrap(),
);
},
);
});
Expand Down
110 changes: 91 additions & 19 deletions crates/egui_flex/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ pub mod flex_widget;

use crate::flex_widget::FlexWidget;
use egui::{
Align, Align2, Frame, Id, InnerResponse, Layout, Margin, Pos2, Rect, Response, Sense, Ui, Vec2,
Widget,
Align, Align2, Frame, Id, InnerResponse, Layout, Margin, Pos2, Rect, Response, Sense, Ui,
UiBuilder, Vec2, Widget,
};
use std::mem;

Expand Down Expand Up @@ -206,9 +206,10 @@ impl Flex {
ui.min_rect().min,
);

let max_item_size = max_item_size.unwrap_or(ui.available_size());
let min_size_rows = self.layout_rows(
&previous_state,
max_item_size.unwrap_or(ui.available_size()),
max_item_size,
gap,
direction,
ui.min_rect().min,
Expand All @@ -219,12 +220,16 @@ impl Flex {
current_row: 0,
current_row_index: 0,
flex: &self,
state: FlexState::default(),
state: FlexState {
items: vec![],
max_item_size,
},
direction,
row_ui: FlexInstance::row_ui(ui, rows.first()),
row_ui: FlexInstance::row_ui(ui, rows.first(), direction),
ui,
rows,
max_item_size,
last_max_item_size: previous_state.max_item_size,
item_spacing: original_item_spacing,
};

Expand Down Expand Up @@ -254,6 +259,15 @@ impl Flex {
// });

if previous_state != instance.state {
// println!("State changed");
// for i in 0..instance.state.items.len() {
// if let Some(prev_item) = previous_state.items.get(i) {
// let item = &instance.state.items[i];
// if prev_item != item {
// dbg!(i, prev_item, item);
// }
// }
// }
instance.ui.ctx().request_discard();
instance.ui.ctx().request_repaint();
}
Expand Down Expand Up @@ -387,11 +401,13 @@ struct ItemState {
size_with_margin: Vec2,
inner_size: Vec2,
margin: Margin,
remeasure_widget: bool,
}

#[derive(Debug, Clone, Default, PartialEq)]
struct FlexState {
items: Vec<ItemState>,
max_item_size: Vec2,
}

pub struct FlexInstance<'a> {
Expand All @@ -404,14 +420,15 @@ pub struct FlexInstance<'a> {
rows: Vec<RowData>,
direction: usize,
row_ui: Ui,
max_item_size: Option<Vec2>,
max_item_size: Vec2,
last_max_item_size: Vec2,
// Original item spacing to store when showing children
item_spacing: Vec2,
}

impl<'a> FlexInstance<'a> {
fn row_ui(parent: &mut Ui, row: Option<&RowData>) -> Ui {
let rect = row
fn row_ui(parent: &mut Ui, row: Option<&RowData>, direction: usize) -> Ui {
let mut rect = row
.map(|row| row.rect.unwrap())
.unwrap_or(parent.max_rect());
let child = parent.child_ui(rect, *parent.layout(), None);
Expand Down Expand Up @@ -458,6 +475,7 @@ impl<'a> FlexInstance<'a> {

let res = self.row_ui.scope(|ui| {
let res = if let Some(row) = row {
let row_item_count = row.items.len();
// TODO: Handle when this is not set (Why doesn't this fail?)
let item_state = row.items.get_mut(self.current_row_index).unwrap();

Expand All @@ -481,8 +499,13 @@ impl<'a> FlexInstance<'a> {
total_size[self.direction] += extra_length;

let available_size = ui.available_rect_before_wrap().size();
total_size[self.direction] =
f32::min(total_size[self.direction], available_size[self.direction]);

// If everything is wrapped we will limit the items size to the containers available
// size to prevent it from growing out of the container
if row_item_count == 1 {
total_size[self.direction] =
f32::min(total_size[self.direction], available_size[self.direction]);
}
total_size[1 - self.direction] = f32::min(
total_size[1 - self.direction],
available_size[1 - self.direction],
Expand Down Expand Up @@ -528,8 +551,7 @@ impl<'a> FlexInstance<'a> {
let mut content_rect =
content_align.align_size_within_rect(inner_size, frame_without_margin);

let max_content_size = self.max_item_size.unwrap_or(self.ui.available_size())
- item_state.margin.sum();
let max_content_size = self.max_item_size - item_state.margin.sum();
// Because we want to allow the content to grow (e.g. in case the text gets longer),
// we set the content_rect's size to match the flex ui's available size.
content_rect.set_width(max_content_size.x);
Expand Down Expand Up @@ -574,6 +596,12 @@ impl<'a> FlexInstance<'a> {
margin: item_state.margin,
parent_min_rect,
max_item_size: max_content_size,
// If the available space grows we want to remeasure the widget, in case
// it's wrapped so it can un-wrap
remeasure_widget: item_state.remeasure_widget
|| self.max_item_size[self.direction]
> self.last_max_item_size[self.direction],
last_inner_size: Some(item_state.inner_size),
},
);
let (_, _r) = ui.allocate_space(child_ui.min_rect().size());
Expand All @@ -599,7 +627,9 @@ impl<'a> FlexInstance<'a> {
frame_rect: rect,
margin: Margin::ZERO,
parent_min_rect: rect,
max_item_size: self.max_item_size.unwrap_or(ui.available_size()),
max_item_size: self.max_item_size,
remeasure_widget: false,
last_inner_size: None,
},
);

Expand All @@ -622,6 +652,7 @@ impl<'a> FlexInstance<'a> {
id: ui.id(),
size_with_margin: (res.child_rect.size() + margin.sum()).round(),
config: item,
remeasure_widget: res.remeasure_widget,
};

(res.inner, item, row_len)
Expand All @@ -639,7 +670,8 @@ impl<'a> FlexInstance<'a> {
if self.current_row_index >= row_len {
self.current_row += 1;
self.current_row_index = 0;
self.row_ui = FlexInstance::row_ui(self.ui, self.rows.get(self.current_row));
self.row_ui =
FlexInstance::row_ui(self.ui, self.rows.get(self.current_row), self.direction);
}

InnerResponse::new(inner, res.response)
Expand Down Expand Up @@ -718,6 +750,8 @@ pub struct FlexContainerUi {
margin: Margin,
parent_min_rect: Rect,
max_item_size: Vec2,
remeasure_widget: bool,
last_inner_size: Option<Vec2>,
}

pub struct FlexContainerResponse<T> {
Expand All @@ -726,6 +760,7 @@ pub struct FlexContainerResponse<T> {
margin_top_left: Vec2,
max_size: Vec2,
container_min_rect: Rect,
remeasure_widget: bool,
}

impl<T> FlexContainerResponse<T> {
Expand All @@ -736,6 +771,7 @@ impl<T> FlexContainerResponse<T> {
margin_top_left: self.margin_top_left,
max_size: self.max_size,
container_min_rect: self.container_min_rect,
remeasure_widget: self.remeasure_widget,
}
}
}
Expand All @@ -755,6 +791,8 @@ impl FlexContainerUi {
margin,
parent_min_rect,
max_item_size,
remeasure_widget: _,
last_inner_size: _,
} = self;

// We will assume that the margin is symmetrical
Expand Down Expand Up @@ -791,6 +829,7 @@ impl FlexContainerUi {
max_size: ui.available_size(),
margin_top_left,
container_min_rect,
remeasure_widget: false,
}
}

Expand All @@ -814,6 +853,8 @@ impl FlexContainerUi {
margin,
parent_min_rect,
max_item_size,
remeasure_widget: _,
last_inner_size: _,
} = self;

// We will assume that the margin is symmetrical
Expand All @@ -837,33 +878,64 @@ impl FlexContainerUi {
max_size: ui.available_size(),
margin_top_left,
container_min_rect,
remeasure_widget: false,
}
}

#[track_caller]
pub fn content_widget(
self,
ui: &mut Ui,
widget: impl Widget,
) -> FlexContainerResponse<Response> {
let margin_top_left = ui.min_rect().min - self.frame_rect.min;
ui.set_width(ui.available_width());
ui.set_height(ui.available_height());
let available_size = ui.available_size();

let response = ui.centered_and_justified(|ui| widget.ui(ui)).inner;
let response = if self.remeasure_widget {
ui.ctx().request_discard();
let mut child_ui = ui.new_child(UiBuilder::new().max_rect(self.content_rect));
child_ui.set_invisible();
let response = child_ui.centered_and_justified(|ui| widget.ui(ui)).inner;

let instrinsic_size = response.intrinsic_size.unwrap_or(Vec2::new(
ui.spacing().interact_size.x,
ui.spacing().interact_size.y,
));
ui.allocate_space(instrinsic_size);

response
} else {
ui.set_width(ui.available_width());
ui.set_height(ui.available_height());
ui.centered_and_justified(|ui| widget.ui(ui)).inner
};

let intrinsic_size = response.intrinsic_size.unwrap_or(Vec2::new(
ui.spacing().interact_size.x,
ui.spacing().interact_size.y,
));
dbg!(intrinsic_size);

// let intrinsic_size = response.rect.size();
// If the size changed in the cross direction the widget might have grown in the main direction
// and wrapped, we need to remeasure the widget (draw it once with full available size)
let remeasure_widget = self.last_inner_size.is_some_and(|last_size| {
last_size[1 - self.direction].round() != intrinsic_size[1 - self.direction].round()
}) && !self.remeasure_widget;

if remeasure_widget {
ui.ctx().request_repaint();
ui.ctx().request_discard();

// dbg!(available_size);
// dbg!(intrinsic_size.round(), self.last_inner_size);
}

FlexContainerResponse {
child_rect: Rect::from_min_size(self.frame_rect.min, intrinsic_size),
inner: response,
max_size: ui.available_size(),
margin_top_left,
container_min_rect: ui.min_rect(),
remeasure_widget,
}
}
}

0 comments on commit 8b9fcd6

Please sign in to comment.