Skip to content

Commit eef2224

Browse files
authored
Replace custom filter layout code with atoms (#11236)
### Related * follow up of #11195 * part of https://linear.app/rerun/project/ability-to-filter-tables-in-the-viewer-8dcb9ff4e6bc/overview * closes #11194 ### What Removes the custom layout code in filter_ui in favor of Atoms. This also changes the color to match the color in the popup. I think the color before was off because Label had a Sense::click which affected the text color. This is on main: <img width="297" height="116" alt="Screenshot 2025-09-16 at 17 43 05" src="https://github.com/user-attachments/assets/f61e445c-cd8d-48b6-a85c-2be49cbce085" /> This is with my changes: <img width="229" height="128" alt="Screenshot 2025-09-16 at 17 44 26" src="https://github.com/user-attachments/assets/0a8b2e29-b151-4ef4-bfed-7a40cd3dfb8f" /> I _do_ think the strong one looks better, so I'm happy to change both. Also the spacing changed (it should use `icon_spacing` now) and I think it's more even but I'm happy to revert that.
1 parent 1a3ba88 commit eef2224

27 files changed

+140
-168
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7729,7 +7729,6 @@ dependencies = [
77297729
"static_assertions",
77307730
"thiserror 1.0.69",
77317731
"tokio",
7732-
"vec1",
77337732
]
77347733

77357734
[[package]]

crates/viewer/re_dataframe_ui/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ nohash-hasher.workspace = true
4646
parking_lot.workspace = true
4747
serde.workspace = true
4848
thiserror.workspace = true
49-
vec1.workspace = true
5049

5150

5251
[dev-dependencies]

crates/viewer/re_dataframe_ui/src/filters/filter_ui.rs

Lines changed: 75 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use std::sync::Arc;
1+
use std::mem;
22

3-
use egui::{Frame, Margin};
3+
use egui::{Atom, AtomLayout, Atoms, Frame, Margin, Sense};
44

55
use re_ui::{SyntaxHighlighting, UiExt as _, syntax_highlighting::SyntaxHighlightedBuilder};
66

@@ -117,65 +117,35 @@ impl FilterState {
117117
let active_index = self.active_filter.take();
118118
let mut remove_idx = None;
119119

120-
// TODO(#11194): ideally, egui would allow wrapping `Frame` widget itself. Remove
121-
// this when it does.
122-
let prepared_uis = self
123-
.filters
124-
.iter()
125-
.map(|filter| filter.prepare_ui(ui))
126-
.collect::<Vec<_>>();
127-
let item_spacing = ui.style().spacing.item_spacing.x;
128-
let available_width = ui.available_width();
129-
let mut rows = vec1::vec1![vec![]];
130-
let mut current_left_position = 0.0;
131-
for (index, prepared_ui) in prepared_uis.iter().enumerate() {
132-
if current_left_position > 0.0
133-
&& current_left_position + prepared_ui.desired_width() > available_width
134-
{
135-
rows.push(vec![]);
136-
current_left_position = 0.0;
137-
}
138-
139-
rows.last_mut().push(index);
140-
current_left_position += prepared_ui.desired_width() + item_spacing;
141-
}
142-
143-
for row in rows {
144-
ui.horizontal(|ui| {
145-
for index in row {
146-
let filter = &mut self.filters[index];
147-
148-
// egui uses this id to store the popup openness and size information,
149-
// so we must invalidate if the filter at a given index changes its
150-
// nature
151-
let filter_id = ui.make_persistent_id(egui::Id::new(index).with(
152-
match filter.operation {
153-
FilterOperation::IntCompares { .. } => "int",
154-
FilterOperation::FloatCompares { .. } => "float",
155-
FilterOperation::StringContains(_) => "string",
156-
FilterOperation::Boolean(_) => "bool",
157-
},
158-
));
159-
160-
let prepared_ui = &prepared_uis[index];
161-
162-
let result =
163-
filter.ui(ui, prepared_ui, filter_id, Some(index) == active_index);
164-
165-
action = action.merge(result.filter_action);
166-
167-
if result.should_delete_filter {
168-
remove_idx = Some(index);
169-
}
120+
ui.horizontal_wrapped(|ui| {
121+
for (index, filter) in self.filters.iter_mut().enumerate() {
122+
// egui uses this id to store the popup openness and size information,
123+
// so we must invalidate if the filter at a given index changes its
124+
// nature
125+
let filter_id = ui.make_persistent_id(egui::Id::new(index).with(
126+
match filter.operation {
127+
FilterOperation::IntCompares { .. } => "int",
128+
FilterOperation::FloatCompares { .. } => "float",
129+
FilterOperation::StringContains(_) => "string",
130+
FilterOperation::Boolean(_) => "bool",
131+
},
132+
));
133+
134+
let result = filter.ui(ui, filter_id, Some(index) == active_index);
135+
136+
action = action.merge(result.filter_action);
137+
138+
if result.should_delete_filter {
139+
remove_idx = Some(index);
170140
}
171-
});
172-
}
141+
}
173142

174-
if let Some(remove_idx) = remove_idx {
175-
self.active_filter = None;
176-
self.filters.remove(remove_idx);
177-
should_commit = true;
178-
}
143+
if let Some(remove_idx) = remove_idx {
144+
self.active_filter = None;
145+
self.filters.remove(remove_idx);
146+
should_commit = true;
147+
}
148+
});
179149
});
180150

181151
action
@@ -188,79 +158,65 @@ struct DisplayFilterUiResult {
188158
should_delete_filter: bool,
189159
}
190160

191-
// TODO(#11194): used by the manual wrapping code. Remove when no longer needed.
192-
struct FilterPreparedUi {
193-
frame: Frame,
194-
galley: Arc<egui::Galley>,
195-
desired_width: f32,
196-
}
197-
198-
impl FilterPreparedUi {
199-
fn desired_width(&self) -> f32 {
200-
self.desired_width
201-
}
202-
}
203-
204161
impl Filter {
205-
/// Prepare the UI for this filter
206-
fn prepare_ui(&self, ui: &egui::Ui) -> FilterPreparedUi {
207-
let layout_job = SyntaxHighlightedBuilder::new()
208-
.with_body(&self.column_name)
209-
.with_keyword(" ")
210-
.with(&self.operation)
211-
.into_job(ui.style());
212-
213-
let galley = ui.fonts(|f| f.layout_job(layout_job));
214-
215-
let frame = Frame::new()
216-
.inner_margin(Margin::symmetric(4, 4))
217-
.stroke(ui.tokens().table_filter_frame_stroke)
218-
.corner_radius(2.0);
219-
220-
let desired_width = galley.size().x
221-
+ ui.style().spacing.item_spacing.x
222-
+ ui.tokens().small_icon_size.x
223-
+ frame.total_margin().sum().x;
224-
225-
FilterPreparedUi {
226-
frame,
227-
galley,
228-
desired_width,
229-
}
162+
pub fn close_button_id() -> egui::Id {
163+
egui::Id::new("filter_close_button")
230164
}
231165

232166
/// UI for a single filter.
233167
#[must_use]
234168
fn ui(
235169
&mut self,
236170
ui: &mut egui::Ui,
237-
prepared_ui: &FilterPreparedUi,
238171
filter_id: egui::Id,
239172
activate_filter: bool,
240173
) -> DisplayFilterUiResult {
241174
let mut should_delete_filter = false;
242175
let mut action_due_to_filter_deletion = FilterUiAction::None;
243176

244-
let response = prepared_ui
245-
.frame
246-
.show(ui, |ui| {
247-
let text_response = ui.add(
248-
egui::Label::new(Arc::clone(&prepared_ui.galley))
249-
.selectable(false)
250-
.sense(egui::Sense::click()),
251-
);
252-
253-
if ui
254-
.small_icon_button(&re_ui::icons::CLOSE_SMALL, "Remove filter")
255-
.clicked()
256-
{
257-
should_delete_filter = true;
258-
action_due_to_filter_deletion = FilterUiAction::CommitStateToBlueprint;
259-
}
177+
let mut atoms = Atoms::default();
260178

261-
text_response
262-
})
263-
.inner;
179+
let layout_job = SyntaxHighlightedBuilder::new()
180+
.with_body_default(&self.column_name)
181+
.with_keyword(" ")
182+
.with(&self.operation)
183+
.into_job(ui.style());
184+
185+
atoms.push_right(layout_job);
186+
187+
atoms.push_right(Atom::custom(
188+
Self::close_button_id(),
189+
ui.tokens().small_icon_size,
190+
));
191+
192+
let frame = Frame::new()
193+
.inner_margin(Margin::symmetric(4, 4))
194+
.stroke(ui.tokens().table_filter_frame_stroke)
195+
.corner_radius(2.0);
196+
197+
let atom_layout = AtomLayout::new(atoms).sense(Sense::click()).frame(frame);
198+
199+
let atom_response = atom_layout.show(ui);
200+
201+
if let Some(rect) = atom_response.rect(Self::close_button_id()) {
202+
// The default padding is (1.0, 0.0), making the button look weird
203+
let button_padding = mem::take(&mut ui.style_mut().spacing.button_padding);
204+
if ui
205+
.place(
206+
rect,
207+
ui.small_icon_button_widget(&re_ui::icons::CLOSE_SMALL, "Remove filter")
208+
// Without small the button would grow to interact_size and be off-center
209+
.small(),
210+
)
211+
.clicked()
212+
{
213+
should_delete_filter = true;
214+
action_due_to_filter_deletion = FilterUiAction::CommitStateToBlueprint;
215+
}
216+
ui.style_mut().spacing.button_padding = button_padding;
217+
}
218+
219+
let response = atom_response.response;
264220

265221
// Should the popup be open?
266222
//
@@ -375,7 +331,7 @@ fn numerical_comparison_operator_ui(
375331
op: &mut ComparisonOperator,
376332
) {
377333
ui.horizontal(|ui| {
378-
ui.label(SyntaxHighlightedBuilder::body(column_name).into_widget_text(ui.style()));
334+
ui.label(SyntaxHighlightedBuilder::body_default(column_name).into_widget_text(ui.style()));
379335

380336
egui::ComboBox::new("comp_op", "")
381337
.selected_text(
@@ -399,7 +355,7 @@ fn numerical_comparison_operator_ui(
399355

400356
pub fn basic_operation_ui(ui: &mut egui::Ui, column_name: &str, operator_text: &str) {
401357
ui.label(
402-
SyntaxHighlightedBuilder::body(column_name)
358+
SyntaxHighlightedBuilder::body_default(column_name)
403359
.with_keyword(" ")
404360
.with_keyword(operator_text)
405361
.into_widget_text(ui.style()),
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

0 commit comments

Comments
 (0)